EnsoGL context abstraction (#3293)

This commit is contained in:
Wojciech Daniło 2022-03-04 15:13:23 +01:00 committed by GitHub
parent d3846578cc
commit f4d236fcd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
184 changed files with 3702 additions and 3283 deletions

1
Cargo.lock generated
View File

@ -1461,6 +1461,7 @@ dependencies = [
name = "ensogl-example-sprite-system" name = "ensogl-example-sprite-system"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"enso-web",
"ensogl-core", "ensogl-core",
"wasm-bindgen", "wasm-bindgen",
] ]

View File

@ -8,13 +8,9 @@ members = [
"build/rust-scripts", "build/rust-scripts",
"lib/rust/*", "lib/rust/*",
"lib/rust/profiler/data", "lib/rust/profiler/data",
"lib/rust/not-used/*",
"integration-test" "integration-test"
] ]
# This directory is not a crate
exclude = ["lib/rust/not-used"]
# The default memebers are those we want to check and test by default. # The default memebers are those we want to check and test by default.
default-members = ["app/gui", "lib/rust/*"] default-members = ["app/gui", "lib/rust/*"]

View File

@ -6,4 +6,4 @@ edition = "2021"
[dependencies] [dependencies]
js-sys = { version = "0.3.28" } js-sys = { version = "0.3.28" }
wasm-bindgen = { version = "0.2.58", features = ["nightly", "serde-serialize"] } wasm-bindgen = { version = "0.2.78", features = ["nightly", "serde-serialize"] }

View File

@ -21,7 +21,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["unbounded_depth"] } serde_json = { version = "1.0", features = ["unbounded_depth"] }
shrinkwraprs = { version = "0.2.1" } shrinkwraprs = { version = "0.2.1" }
uuid = { version = "0.8", features = ["serde", "v5", "wasm-bindgen"] } uuid = { version = "0.8", features = ["serde", "v5", "wasm-bindgen"] }
wasm-bindgen = { version = "0.2.58" } wasm-bindgen = { version = "0.2.78" }
[dev-dependencies] [dev-dependencies]
wasm-bindgen-test = { version = "0.3.8" } wasm-bindgen-test = { version = "0.3.8" }

View File

@ -14,7 +14,7 @@ span-tree = { path = "../../span-tree" }
enso-web = { path = "../../../../../lib/rust/web" } enso-web = { path = "../../../../../lib/rust/web" }
enso-prelude = { path = "../../../../../lib/rust/prelude"} enso-prelude = { path = "../../../../../lib/rust/prelude"}
enso-logger = { path = "../../../../../lib/rust/logger"} enso-logger = { path = "../../../../../lib/rust/logger"}
wasm-bindgen = { version = "0.2.58", features = [ wasm-bindgen = { version = "0.2.78", features = [
"nightly", "nightly",
"serde-serialize" "serde-serialize"
] } ] }

View File

@ -13,6 +13,9 @@ use futures::task::LocalFutureObj;
use futures::task::LocalSpawn; use futures::task::LocalSpawn;
use futures::task::SpawnError; use futures::task::SpawnError;
/// An alias for a main animation loop.
pub type MainLoop = animation::Loop<Box<dyn FnMut(animation::TimeInfo)>>;
/// Executor. Uses a single-threaded `LocalPool` underneath, relying on ensogl's /// Executor. Uses a single-threaded `LocalPool` underneath, relying on ensogl's
/// `animation::DynamicLoop` to do as much progress as possible on every animation frame. /// `animation::DynamicLoop` to do as much progress as possible on every animation frame.
#[derive(Debug)] #[derive(Debug)]
@ -22,7 +25,7 @@ pub struct EventLoopExecutor {
/// Executor's spawner handle. /// Executor's spawner handle.
pub spawner: LocalSpawner, pub spawner: LocalSpawner,
/// Event loop that calls us on each frame. /// Event loop that calls us on each frame.
event_loop: Option<animation::DynamicLoop>, event_loop: Option<MainLoop>,
/// Handle to the callback - if dropped, loop would have stopped calling us. /// Handle to the callback - if dropped, loop would have stopped calling us.
/// Also owns a shared handle to the `executor`. /// Also owns a shared handle to the `executor`.
cb_handle: Option<callback::Handle>, cb_handle: Option<callback::Handle>,
@ -44,14 +47,14 @@ impl EventLoopExecutor {
///loop will live as long as this executor. ///loop will live as long as this executor.
pub fn new_running() -> EventLoopExecutor { pub fn new_running() -> EventLoopExecutor {
let mut executor = EventLoopExecutor::new(); let mut executor = EventLoopExecutor::new();
executor.start_running(animation::DynamicLoop::new()); executor.start_running();
executor executor
} }
/// Returns a callback compatible with `animation::DynamicLoop` that once called shall /// Returns a callback compatible with `animation::DynamicLoop` that once called shall
/// attempt achieving as much progress on this executor's tasks as possible /// attempt achieving as much progress on this executor's tasks as possible
/// without stalling. /// without stalling.
pub fn runner(&self) -> impl animation::DynamicLoopCallback { pub fn runner(&self) -> impl FnMut(animation::TimeInfo) {
let executor = self.executor.clone(); let executor = self.executor.clone();
move |_| { move |_| {
// Safe, because this is the only place borrowing executor and loop // Safe, because this is the only place borrowing executor and loop
@ -67,11 +70,8 @@ impl EventLoopExecutor {
/// ///
/// The executor will keep copy of this loop handle, so caller is not /// The executor will keep copy of this loop handle, so caller is not
/// required to keep it alive. /// required to keep it alive.
pub fn start_running(&mut self, event_loop: animation::DynamicLoop) { fn start_running(&mut self) {
let cb = self.runner(); self.event_loop = Some(MainLoop::new(Box::new(self.runner())));
self.cb_handle = Some(event_loop.on_frame(cb));
self.event_loop = Some(event_loop);
} }
/// Stops event loop (previously assigned by `run` method) from calling this /// Stops event loop (previously assigned by `run` method) from calling this

View File

@ -62,7 +62,7 @@ impl Ide {
fn alive_log_sending_loop(&self) -> impl Future<Output = ()> + 'static { fn alive_log_sending_loop(&self) -> impl Future<Output = ()> + 'static {
let network = &self.network; let network = &self.network;
let scene = self.ensogl_app.display.scene(); let scene = &self.ensogl_app.display.default_scene;
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;
let keyboard = &scene.keyboard.frp; let keyboard = &scene.keyboard.frp;

View File

@ -63,9 +63,9 @@ impl Initializer {
let executor = setup_global_executor(); let executor = setup_global_executor();
executor::global::spawn(async move { executor::global::spawn(async move {
let ide = self.start().await; let ide = self.start().await;
web::get_element_by_id("loader") web::document
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())) .get_element_by_id("loader")
.ok(); .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()));
std::mem::forget(ide); std::mem::forget(ide);
}); });
std::mem::forget(executor); std::mem::forget(executor);
@ -75,8 +75,7 @@ impl Initializer {
pub async fn start(self) -> Result<Ide, FailedIde> { pub async fn start(self) -> Result<Ide, FailedIde> {
info!(self.logger, "Starting IDE with the following config: {self.config:?}"); info!(self.logger, "Starting IDE with the following config: {self.config:?}");
let root_element = web::get_html_element_by_id("root").unwrap(); let ensogl_app = ensogl::application::Application::new("root");
let ensogl_app = ensogl::application::Application::new(&root_element);
Initializer::register_views(&ensogl_app); Initializer::register_views(&ensogl_app);
let view = ensogl_app.new_view::<ide_view::root::View>(); let view = ensogl_app.new_view::<ide_view::root::View>();

View File

@ -67,7 +67,6 @@ pub mod transport;
pub use crate::ide::*; pub use crate::ide::*;
pub use ide_view as view; pub use ide_view as view;
use ensogl::system::web;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
// Those imports are required to have all EnsoGL examples entry points visible in IDE. // Those imports are required to have all EnsoGL examples entry points visible in IDE.
@ -84,7 +83,6 @@ pub mod prelude {
pub use ast::prelude::*; pub use ast::prelude::*;
pub use enso_prelude::*; pub use enso_prelude::*;
pub use ensogl::prelude::*; pub use ensogl::prelude::*;
pub use wasm_bindgen::prelude::*;
pub use crate::constants; pub use crate::constants;
pub use crate::controller; pub use crate::controller;
@ -116,8 +114,6 @@ pub mod prelude {
#[wasm_bindgen] #[wasm_bindgen]
#[allow(dead_code)] #[allow(dead_code)]
pub fn entry_point_ide() { pub fn entry_point_ide() {
web::forward_panic_hook_to_error();
ensogl_text_msdf_sys::run_once_initialized(|| { ensogl_text_msdf_sys::run_once_initialized(|| {
// Logging of build information. // Logging of build information.
#[cfg(debug_assertions)] #[cfg(debug_assertions)]

View File

@ -15,6 +15,7 @@ use crate::executor::global::spawn_stream_handler;
use crate::presenter::graph::state::State; use crate::presenter::graph::state::State;
use enso_frp as frp; use enso_frp as frp;
use enso_web::traits::*;
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
use ide_view as view; use ide_view as view;
use ide_view::graph_editor::component::node as node_view; use ide_view::graph_editor::component::node as node_view;
@ -22,6 +23,7 @@ use ide_view::graph_editor::component::visualization as visualization_view;
use ide_view::graph_editor::EdgeEndpoint; use ide_view::graph_editor::EdgeEndpoint;
// =============== // ===============
// === Aliases === // === Aliases ===
// =============== // ===============
@ -609,6 +611,30 @@ impl Graph {
impl controller::upload::DataProvider for ensogl_drop_manager::File { impl controller::upload::DataProvider for ensogl_drop_manager::File {
fn next_chunk(&mut self) -> LocalBoxFuture<FallibleResult<Option<Vec<u8>>>> { fn next_chunk(&mut self) -> LocalBoxFuture<FallibleResult<Option<Vec<u8>>>> {
self.read_chunk().map(|f| f.map_err(|e| e.into())).boxed_local() self.read_chunk()
.map(|f| f.map_err(|e| StringError::new(e.print_to_string()).into()))
.boxed_local()
}
}
// =============
// === Error ===
// =============
/// Generic error representation. This is used only once in the lines above. Probably should be
/// removed in the future.
#[derive(Debug, Fail)]
#[fail(display = "{}", message)]
pub struct StringError {
message: String,
}
impl StringError {
/// Constructor.
pub fn new(message: impl Into<String>) -> StringError {
let message = message.into();
StringError { message }
} }
} }

View File

@ -3,7 +3,7 @@
use crate::prelude::*; use crate::prelude::*;
use enso_web::event::listener::Slot; use enso_web::event::listener::Slot;
use enso_web::js_to_string; use enso_web::traits::*;
use failure::Error; use failure::Error;
use futures::channel::mpsc; use futures::channel::mpsc;
use json_rpc::Transport; use json_rpc::Transport;
@ -34,8 +34,8 @@ pub enum ConnectingError {
impl ConnectingError { impl ConnectingError {
/// Create a `ConstructionError` value from a JS value describing an error. /// Create a `ConstructionError` value from a JS value describing an error.
pub fn construction_error(js_val: impl AsRef<JsValue>) -> Self { pub fn construction_error(js_val: impl AsRef<enso_web::JsValue>) -> Self {
let text = js_to_string(js_val); let text = js_val.as_ref().print_to_string();
ConnectingError::ConstructionError(text) ConnectingError::ConstructionError(text)
} }
} }
@ -54,8 +54,8 @@ pub enum SendingError {
impl SendingError { impl SendingError {
/// Constructs from the error yielded by one of the JS's WebSocket sending functions. /// Constructs from the error yielded by one of the JS's WebSocket sending functions.
pub fn from_send_error(error: JsValue) -> SendingError { pub fn from_send_error(error: wasm_bindgen::JsValue) -> SendingError {
SendingError::FailedToSend(js_to_string(&error)) SendingError::FailedToSend(error.print_to_string())
} }
} }
@ -192,7 +192,7 @@ impl Model {
} }
/// Close the socket. /// Close the socket.
pub fn close(&mut self, reason: &str) -> Result<(), JsValue> { pub fn close(&mut self, reason: &str) -> Result<(), wasm_bindgen::JsValue> {
// If socket was manually requested to close, it should not try to reconnect then. // If socket was manually requested to close, it should not try to reconnect then.
self.auto_reconnect = false; self.auto_reconnect = false;
let normal_closure = 1000; let normal_closure = 1000;
@ -229,7 +229,7 @@ impl Model {
/// Establish a new WS connection, using the same URL as the previous one. /// Establish a new WS connection, using the same URL as the previous one.
/// All callbacks will be transferred to the new connection. /// All callbacks will be transferred to the new connection.
pub fn reconnect(&mut self) -> Result<(), JsValue> { pub fn reconnect(&mut self) -> Result<(), wasm_bindgen::JsValue> {
if !self.auto_reconnect { if !self.auto_reconnect {
return Err(js_sys::Error::new("Reconnecting has been disabled").into()); return Err(js_sys::Error::new("Reconnecting has been disabled").into());
} }
@ -256,7 +256,7 @@ impl Drop for Model {
if let Err(e) = self.close("Rust Value has been dropped.") { if let Err(e) = self.close("Rust Value has been dropped.") {
error!( error!(
self.logger, self.logger,
"Error when closing socket due to being dropped: {js_to_string(&e)}" "Error when closing socket due to being dropped: {e.print_to_string()}"
) )
} }
} }
@ -303,7 +303,7 @@ impl WebSocket {
move |_| { move |_| {
if let Some(model) = model.upgrade() { if let Some(model) = model.upgrade() {
if let Err(e) = model.borrow_mut().reconnect() { if let Err(e) = model.borrow_mut().reconnect() {
error!(logger, "Failed to reconnect: {js_to_string(&e)}"); error!(logger, "Failed to reconnect: {e.print_to_string()}");
} }
} }
} }
@ -379,7 +379,7 @@ impl WebSocket {
/// ///
/// WARNING: `f` works under borrow_mut and must not give away control. /// WARNING: `f` works under borrow_mut and must not give away control.
fn send_with_open_socket<F, R>(&mut self, f: F) -> Result<R, Error> fn send_with_open_socket<F, R>(&mut self, f: F) -> Result<R, Error>
where F: FnOnce(&mut web_sys::WebSocket) -> Result<R, JsValue> { where F: FnOnce(&mut web_sys::WebSocket) -> Result<R, wasm_bindgen::JsValue> {
// Sending through the closed WebSocket can return Ok() with error only // Sending through the closed WebSocket can return Ok() with error only
// appearing in the log. We explicitly check for this to get failure as // appearing in the log. We explicitly check for this to get failure as
// early as possible. // early as possible.
@ -433,7 +433,7 @@ impl Transport for WebSocket {
let event = TransportEvent::BinaryMessage(binary_data); let event = TransportEvent::BinaryMessage(binary_data);
channel::emit(&transmitter_copy, event); channel::emit(&transmitter_copy, event);
} else { } else {
info!(logger_copy, "Received other kind of message: {js_to_string(e.data())}."); info!(logger_copy, "Received other kind of message: {e.data().print_to_string()}.");
} }
}); });

View File

@ -30,7 +30,7 @@ ordered-float = { version = "2.7.0" }
serde_json = { version = "1.0" } serde_json = { version = "1.0" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] } uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] }
wasm-bindgen = { version = "0.2.58", features = [ wasm-bindgen = { version = "0.2.78", features = [
"nightly", "nightly",
"serde-serialize" "serde-serialize"
] } ] }

View File

@ -17,4 +17,4 @@ ide-view = { path = "../.." }
parser = { path = "../../../language/parser" } parser = { path = "../../../language/parser" }
span-tree = { path = "../../../language/span-tree" } span-tree = { path = "../../../language/span-tree" }
uuid = { version = "0.8", features = ["v4", "wasm-bindgen"] } uuid = { version = "0.8", features = ["v4", "wasm-bindgen"] }
wasm-bindgen = { version = "0.2.58", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -45,10 +45,8 @@ const STUB_MODULE: &str = "from Base import all\n\nmain = IO.println \"Hello\"\n
#[wasm_bindgen] #[wasm_bindgen]
#[allow(dead_code)] #[allow(dead_code)]
pub fn entry_point_interface() { pub fn entry_point_interface() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
run_once_initialized(|| { run_once_initialized(|| {
let app = Application::new(&web::get_html_element_by_id("root").unwrap()); let app = Application::new("root");
init(&app); init(&app);
mem::forget(app); mem::forget(app);
}); });
@ -100,10 +98,10 @@ impl DummyTypeGenerator {
// ======================== // ========================
fn init(app: &Application) { fn init(app: &Application) {
let _bg = app.display.scene().style_sheet.var(theme::application::background); let _bg = app.display.default_scene.style_sheet.var(theme::application::background);
let world = &app.display; let world = &app.display;
let scene = world.scene(); let scene = &world.default_scene;
let camera = scene.camera(); let camera = scene.camera();
let navigator = Navigator::new(scene, &camera); let navigator = Navigator::new(scene, &camera);
@ -252,7 +250,9 @@ fn init(app: &Application) {
let mut to_theme_switch = 100; let mut to_theme_switch = 100;
world world
.on_frame(move |_| { .on
.before_frame
.add(move |_| {
let _keep_alive = &navigator; let _keep_alive = &navigator;
let _keep_alive = &root_view; let _keep_alive = &root_view;
@ -292,9 +292,9 @@ fn init(app: &Application) {
// Temporary code removing the web-loader instance. // Temporary code removing the web-loader instance.
// To be changed in the future. // To be changed in the future.
if was_rendered && !loader_hidden { if was_rendered && !loader_hidden {
web::get_element_by_id("loader") web::document
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())) .get_element_by_id("loader")
.ok(); .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()));
loader_hidden = true; loader_hidden = true;
} }
was_rendered = true; was_rendered = true;

View File

@ -16,4 +16,4 @@ ide-view = { path = "../.." }
js-sys = { version = "0.3.28" } js-sys = { version = "0.3.28" }
nalgebra = { version = "0.26.1" } nalgebra = { version = "0.26.1" }
serde_json = { version = "1.0" } serde_json = { version = "1.0" }
wasm-bindgen = { version = "0.2.58", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }

View File

@ -11,6 +11,7 @@
use ensogl::prelude::*; use ensogl::prelude::*;
use ensogl::animation;
use ensogl::application::Application; use ensogl::application::Application;
use ensogl::display::navigation::navigator::Navigator; use ensogl::display::navigation::navigator::Navigator;
use ensogl::system::web; use ensogl::system::web;
@ -22,6 +23,8 @@ use js_sys::Math::sin;
use nalgebra::Vector2; use nalgebra::Vector2;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
fn generate_data(seconds: f64) -> Vec<Vector2<f32>> { fn generate_data(seconds: f64) -> Vec<Vector2<f32>> {
let mut data = Vec::new(); let mut data = Vec::new();
for x in 0..100 { for x in 0..100 {
@ -89,10 +92,8 @@ fn constructor_graph() -> visualization::java_script::Definition {
#[wasm_bindgen] #[wasm_bindgen]
#[allow(dead_code, missing_docs)] #[allow(dead_code, missing_docs)]
pub fn entry_point_visualization() { pub fn entry_point_visualization() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
run_once_initialized(|| { run_once_initialized(|| {
let app = Application::new(&web::get_html_element_by_id("root").unwrap()); let app = Application::new("root");
init(&app); init(&app);
std::mem::forget(app); std::mem::forget(app);
}); });
@ -100,7 +101,7 @@ pub fn entry_point_visualization() {
fn init(app: &Application) { fn init(app: &Application) {
let world = &app.display; let world = &app.display;
let scene = world.scene(); let scene = &world.default_scene;
let camera = scene.camera(); let camera = scene.camera();
let navigator = Navigator::new(scene, &camera); let navigator = Navigator::new(scene, &camera);
let registry = Registry::new(); let registry = Registry::new();
@ -124,7 +125,9 @@ fn init(app: &Application) {
let mut was_rendered = false; let mut was_rendered = false;
let mut loader_hidden = false; let mut loader_hidden = false;
world world
.on_frame(move |time_info| { .on
.before_frame
.add(move |time_info: animation::TimeInfo| {
let _keep_alive = &navigator; let _keep_alive = &navigator;
let data = generate_data((time_info.local / 1000.0).into()); let data = generate_data((time_info.local / 1000.0).into());
@ -137,9 +140,9 @@ fn init(app: &Application) {
// Temporary code removing the web-loader instance. // Temporary code removing the web-loader instance.
// To be changed in the future. // To be changed in the future.
if was_rendered && !loader_hidden { if was_rendered && !loader_hidden {
web::get_element_by_id("loader") web::document
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap())) .get_element_by_id("loader")
.ok(); .map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()));
loader_hidden = true; loader_hidden = true;
} }
was_rendered = true; was_rendered = true;

View File

@ -33,7 +33,7 @@ serde_json = { version = "1.0" }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sourcemap = "6.0" sourcemap = "6.0"
uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] } uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] }
wasm-bindgen = { version = "0.2.58", features = ["nightly", "serde-serialize"] } wasm-bindgen = { version = "0.2.78", features = ["nightly", "serde-serialize"] }
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3.4" version = "0.3.4"

View File

@ -14,8 +14,7 @@ use ensogl::display::scene::Scene;
use ensogl::display::shape::primitive::StyleWatch; use ensogl::display::shape::primitive::StyleWatch;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::AttributeSetter; use ensogl::system::web::traits::*;
use ensogl::system::web::StyleSetter;
use ensogl_hardcoded_theme; use ensogl_hardcoded_theme;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@ -160,7 +159,7 @@ impl Model {
/// Constructor. /// Constructor.
fn new(scene: Scene) -> Self { fn new(scene: Scene) -> Self {
let logger = Logger::new("RawText"); let logger = Logger::new("RawText");
let div = web::create_div(); let div = web::document.create_div_or_panic();
let dom = DomSymbol::new(&div); let dom = DomSymbol::new(&div);
let size = Rc::new(Cell::new(Vector2(200.0, 200.0))); let size = Rc::new(Cell::new(Vector2(200.0, 200.0)));
let displayed = Rc::new(CloneCell::new(Kind::Panic)); let displayed = Rc::new(CloneCell::new(Kind::Panic));
@ -169,15 +168,15 @@ impl Model {
let styles = StyleWatch::new(&scene.style_sheet); let styles = StyleWatch::new(&scene.style_sheet);
let padding_text = format!("{}px", PADDING_TEXT); let padding_text = format!("{}px", PADDING_TEXT);
dom.dom().set_attribute_or_warn("class", "visualization scrollable", &logger); dom.dom().set_attribute_or_warn("class", "visualization scrollable");
dom.dom().set_style_or_warn("overflow-x", "hidden", &logger); dom.dom().set_style_or_warn("overflow-x", "hidden");
dom.dom().set_style_or_warn("overflow-y", "auto", &logger); dom.dom().set_style_or_warn("overflow-y", "auto");
dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook", &logger); dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook");
dom.dom().set_style_or_warn("font-size", "12px", &logger); dom.dom().set_style_or_warn("font-size", "12px");
dom.dom().set_style_or_warn("border-radius", "14px", &logger); dom.dom().set_style_or_warn("border-radius", "14px");
dom.dom().set_style_or_warn("padding-left", &padding_text, &logger); dom.dom().set_style_or_warn("padding-left", &padding_text);
dom.dom().set_style_or_warn("padding-top", &padding_text, &logger); dom.dom().set_style_or_warn("padding-top", &padding_text);
dom.dom().set_style_or_warn("pointer-events", "auto", &logger); dom.dom().set_style_or_warn("pointer-events", "auto");
scene.dom.layers.back.manage(&dom); scene.dom.layers.back.manage(&dom);
Model { logger, dom, size, styles, displayed, messages, scene }.init() Model { logger, dom, size, styles, displayed, messages, scene }.init()
@ -243,7 +242,7 @@ impl Model {
let green = text_color.green * 255.0; let green = text_color.green * 255.0;
let blue = text_color.blue * 255.0; let blue = text_color.blue * 255.0;
let text_color = format!("rgba({},{},{},{})", red, green, blue, text_color.alpha); let text_color = format!("rgba({},{},{},{})", red, green, blue, text_color.alpha);
self.dom.dom().set_style_or_warn("color", text_color, &self.logger); self.dom.dom().set_style_or_warn("color", text_color);
} }
fn set_layer(&self, layer: Layer) { fn set_layer(&self, layer: Layer) {

View File

@ -11,8 +11,7 @@ use ensogl::display::scene::Scene;
use ensogl::display::shape::primitive::StyleWatch; use ensogl::display::shape::primitive::StyleWatch;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::AttributeSetter; use ensogl::system::web::traits::*;
use ensogl::system::web::StyleSetter;
use ensogl_hardcoded_theme; use ensogl_hardcoded_theme;
@ -85,7 +84,7 @@ impl RawTextModel {
/// Constructor. /// Constructor.
fn new(scene: Scene) -> Self { fn new(scene: Scene) -> Self {
let logger = Logger::new("RawText"); let logger = Logger::new("RawText");
let div = web::create_div(); let div = web::document.create_div_or_panic();
let dom = DomSymbol::new(&div); let dom = DomSymbol::new(&div);
let size = Rc::new(Cell::new(Vector2(200.0, 200.0))); let size = Rc::new(Cell::new(Vector2(200.0, 200.0)));
@ -100,16 +99,16 @@ impl RawTextModel {
let text_color = format!("rgba({},{},{},{})", _red, _green, _blue, text_color.alpha); let text_color = format!("rgba({},{},{},{})", _red, _green, _blue, text_color.alpha);
let padding_text = format!("{}px", PADDING_TEXT); let padding_text = format!("{}px", PADDING_TEXT);
dom.dom().set_attribute_or_warn("class", "visualization scrollable", &logger); dom.dom().set_attribute_or_warn("class", "visualization scrollable");
dom.dom().set_style_or_warn("white-space", "pre", &logger); dom.dom().set_style_or_warn("white-space", "pre");
dom.dom().set_style_or_warn("overflow-y", "auto", &logger); dom.dom().set_style_or_warn("overflow-y", "auto");
dom.dom().set_style_or_warn("overflow-x", "auto", &logger); dom.dom().set_style_or_warn("overflow-x", "auto");
dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook", &logger); dom.dom().set_style_or_warn("font-family", "DejaVuSansMonoBook");
dom.dom().set_style_or_warn("font-size", "12px", &logger); dom.dom().set_style_or_warn("font-size", "12px");
dom.dom().set_style_or_warn("padding-left", &padding_text, &logger); dom.dom().set_style_or_warn("padding-left", &padding_text);
dom.dom().set_style_or_warn("padding-top", &padding_text, &logger); dom.dom().set_style_or_warn("padding-top", &padding_text);
dom.dom().set_style_or_warn("color", text_color, &logger); dom.dom().set_style_or_warn("color", text_color);
dom.dom().set_style_or_warn("pointer-events", "auto", &logger); dom.dom().set_style_or_warn("pointer-events", "auto");
scene.dom.layers.back.manage(&dom); scene.dom.layers.back.manage(&dom);
RawTextModel { logger, dom, size, scene }.init() RawTextModel { logger, dom, size, scene }.init()

View File

@ -97,7 +97,7 @@ impl AddNodeButton {
pub fn new(app: &Application) -> Self { pub fn new(app: &Application) -> Self {
let view = ensogl_component::button::View::new(app); let view = ensogl_component::button::View::new(app);
let network = frp::Network::new("AddNodeButton"); let network = frp::Network::new("AddNodeButton");
let scene = app.display.scene(); let scene = &app.display.default_scene;
let camera = scene.camera(); let camera = scene.camera();
let style_watch = StyleWatchFrp::new(&scene.style_sheet); let style_watch = StyleWatchFrp::new(&scene.style_sheet);

View File

@ -185,7 +185,7 @@ impl BreadcrumbsModel {
/// The `gap_width` describes an empty space on the left of all the content. This space will be /// The `gap_width` describes an empty space on the left of all the content. This space will be
/// covered by the background and is intended to make room for windows control buttons. /// covered by the background and is intended to make room for windows control buttons.
pub fn new(app: Application, frp: &Frp) -> Self { pub fn new(app: Application, frp: &Frp) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let project_name = app.new_view(); let project_name = app.new_view();
let logger = Logger::new("Breadcrumbs"); let logger = Logger::new("Breadcrumbs");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
@ -450,7 +450,7 @@ pub struct Breadcrumbs {
impl Breadcrumbs { impl Breadcrumbs {
/// Constructor. /// Constructor.
pub fn new(app: Application) -> Self { pub fn new(app: Application) -> Self {
let scene = app.display.scene().clone_ref(); let scene = app.display.default_scene.clone_ref();
let frp = Frp::new(); let frp = Frp::new();
let model = Rc::new(BreadcrumbsModel::new(app, &frp)); let model = Rc::new(BreadcrumbsModel::new(app, &frp));
let network = &frp.network; let network = &frp.network;

View File

@ -279,7 +279,7 @@ impl BreadcrumbModel {
method_pointer: &MethodPointer, method_pointer: &MethodPointer,
expression_id: &ast::Id, expression_id: &ast::Id,
) -> Self { ) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("Breadcrumbs"); let logger = Logger::new("Breadcrumbs");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let view_logger = Logger::new_sub(&logger, "view_logger"); let view_logger = Logger::new_sub(&logger, "view_logger");
@ -492,7 +492,7 @@ impl Breadcrumb {
let frp = Frp::new(); let frp = Frp::new();
let model = Rc::new(BreadcrumbModel::new(app, &frp, method_pointer, expression_id)); let model = Rc::new(BreadcrumbModel::new(app, &frp, method_pointer, expression_id));
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795) // system (#795)

View File

@ -134,7 +134,7 @@ impl ProjectNameModel {
/// Constructor. /// Constructor.
fn new(app: &Application) -> Self { fn new(app: &Application) -> Self {
let app = app.clone_ref(); let app = app.clone_ref();
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("ProjectName"); let logger = Logger::new("ProjectName");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
@ -251,7 +251,7 @@ impl ProjectName {
let frp = Frp::new(); let frp = Frp::new();
let model = Rc::new(ProjectNameModel::new(app)); let model = Rc::new(ProjectNameModel::new(app));
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let text = &model.text_field.frp; let text = &model.text_field.frp;
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795) // system (#795)

View File

@ -1161,7 +1161,7 @@ impl Edge {
/// Constructor. /// Constructor.
pub fn new(app: &Application) -> Self { pub fn new(app: &Application) -> Self {
let network = frp::Network::new("node_edge"); let network = frp::Network::new("node_edge");
let data = Rc::new(EdgeModelData::new(app.display.scene(), &network)); let data = Rc::new(EdgeModelData::new(&app.display.default_scene, &network));
let model = Rc::new(EdgeModel { data }); let model = Rc::new(EdgeModel { data });
Self { model, network }.init(app) Self { model, network }.init(app)
} }
@ -1182,7 +1182,7 @@ impl Edge {
let shape_events = &self.frp.shape_events; let shape_events = &self.frp.shape_events;
let edge_color = color::Animation::new(network); let edge_color = color::Animation::new(network);
let edge_focus_color = color::Animation::new(network); let edge_focus_color = color::Animation::new(network);
let _style = StyleWatch::new(&app.display.scene().style_sheet); let _style = StyleWatch::new(&app.display.default_scene.style_sheet);
model.data.front.register_proxy_frp(network, &input.shape_events); model.data.front.register_proxy_frp(network, &input.shape_events);
model.data.back.register_proxy_frp(network, &input.shape_events); model.data.back.register_proxy_frp(network, &input.shape_events);

View File

@ -426,7 +426,7 @@ impl NodeModel {
/// Constructor. /// Constructor.
pub fn new(app: &Application, registry: visualization::Registry) -> Self { pub fn new(app: &Application, registry: visualization::Registry) -> Self {
ensogl::shapes_order_dependencies! { ensogl::shapes_order_dependencies! {
app.display.scene() => { app.display.default_scene => {
//TODO[ao] The two lines below should not be needed - the ordering should be //TODO[ao] The two lines below should not be needed - the ordering should be
// transitive. But removing them causes a visual glitches described in // transitive. But removing them causes a visual glitches described in
// https://github.com/enso-org/ide/issues/1624 // https://github.com/enso-org/ide/issues/1624
@ -449,7 +449,7 @@ impl NodeModel {
} }
} }
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("node"); let logger = Logger::new("node");
let main_logger = Logger::new_sub(&logger, "main_area"); let main_logger = Logger::new_sub(&logger, "main_area");
@ -495,7 +495,7 @@ impl NodeModel {
let output = output::Area::new(&logger, app); let output = output::Area::new(&logger, app);
display_object.add_child(&output); display_object.add_child(&output);
let style = StyleWatchFrp::new(&app.display.scene().style_sheet); let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let comment = text::Area::new(app); let comment = text::Area::new(app);
display_object.add_child(&comment); display_object.add_child(&comment);
@ -624,7 +624,7 @@ impl Node {
// in https://github.com/enso-org/ide/issues/1031 // in https://github.com/enso-org/ide/issues/1031
// let comment_color = color::Animation::new(network); // let comment_color = color::Animation::new(network);
let error_color_anim = color::Animation::new(network); let error_color_anim = color::Animation::new(network);
let style = StyleWatch::new(&app.display.scene().style_sheet); let style = StyleWatch::new(&app.display.default_scene.style_sheet);
let style_frp = &model.style; let style_frp = &model.style;
let action_bar = &model.action_bar.frp; let action_bar = &model.action_bar.frp;
// Hook up the display object position updates to the node's FRP. Required to calculate the // Hook up the display object position updates to the node's FRP. Required to calculate the

View File

@ -134,7 +134,7 @@ struct Model {
impl Model { impl Model {
fn new(logger: impl AnyLogger, app: &Application) -> Self { fn new(logger: impl AnyLogger, app: &Application) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new_sub(logger, "ActionBar"); let logger = Logger::new_sub(logger, "ActionBar");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let hover_area = hover_area::View::new(&logger); let hover_area = hover_area::View::new(&logger);

View File

@ -9,7 +9,7 @@ use ensogl::display::shape::StyleWatch;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::display::Scene; use ensogl::display::Scene;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::StyleSetter; use ensogl::system::web::traits::*;
use ensogl_component::shadow; use ensogl_component::shadow;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
@ -93,7 +93,7 @@ impl Container {
let scene = scene.clone_ref(); let scene = scene.clone_ref();
let logger = Logger::new("error::Container"); let logger = Logger::new("error::Container");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let background_dom = Self::create_background_dom(&logger, &scene); let background_dom = Self::create_background_dom(&scene);
let visualization = error_visualization::Error::new(&scene); let visualization = error_visualization::Error::new(&scene);
display_object.add_child(&background_dom); display_object.add_child(&background_dom);
@ -102,7 +102,7 @@ impl Container {
Self { logger, visualization, scene, background_dom, display_object } Self { logger, visualization, scene, background_dom, display_object }
} }
fn create_background_dom(logger: &Logger, scene: &Scene) -> DomSymbol { fn create_background_dom(scene: &Scene) -> DomSymbol {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795) // system (#795)
let styles = StyleWatch::new(&scene.style_sheet); let styles = StyleWatch::new(&scene.style_sheet);
@ -113,21 +113,21 @@ impl Container {
let bg_blue = bg_color.blue * 255.0; let bg_blue = bg_color.blue * 255.0;
let bg_hex = format!("rgba({},{},{},{})", bg_red, bg_green, bg_blue, bg_color.alpha); let bg_hex = format!("rgba({},{},{},{})", bg_red, bg_green, bg_blue, bg_color.alpha);
let div = web::create_div(); let div = web::document.create_div_or_panic();
let background_dom = DomSymbol::new(&div); let background_dom = DomSymbol::new(&div);
let (width, height) = SIZE; let (width, height) = SIZE;
let width = format!("{}.px", width); let width = format!("{}.px", width);
let height = format!("{}.px", height); let height = format!("{}.px", height);
let z_index = Z_INDEX.to_string(); let z_index = Z_INDEX.to_string();
let border_radius = format!("{}.px", BORDER_RADIUS); let border_radius = format!("{}.px", BORDER_RADIUS);
background_dom.dom().set_style_or_warn("width", width, logger); background_dom.dom().set_style_or_warn("width", width);
background_dom.dom().set_style_or_warn("height", height, logger); background_dom.dom().set_style_or_warn("height", height);
background_dom.dom().set_style_or_warn("z-index", z_index, logger); background_dom.dom().set_style_or_warn("z-index", z_index);
background_dom.dom().set_style_or_warn("overflow-y", "auto", logger); background_dom.dom().set_style_or_warn("overflow-y", "auto");
background_dom.dom().set_style_or_warn("overflow-x", "auto", logger); background_dom.dom().set_style_or_warn("overflow-x", "auto");
background_dom.dom().set_style_or_warn("background", bg_hex, logger); background_dom.dom().set_style_or_warn("background", bg_hex);
background_dom.dom().set_style_or_warn("border-radius", border_radius, logger); background_dom.dom().set_style_or_warn("border-radius", border_radius);
shadow::add_to_dom_element(&background_dom, &styles, logger); shadow::add_to_dom_element(&background_dom, &styles);
background_dom background_dom
} }

View File

@ -245,8 +245,8 @@ impl Model {
let label = app.new_view::<text::Area>(); let label = app.new_view::<text::Area>();
let id_crumbs_map = default(); let id_crumbs_map = default();
let expression = default(); let expression = default();
let styles = StyleWatch::new(&app.display.scene().style_sheet); let styles = StyleWatch::new(&app.display.default_scene.style_sheet);
let styles_frp = StyleWatchFrp::new(&app.display.scene().style_sheet); let styles_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
display_object.add_child(&label); display_object.add_child(&label);
display_object.add_child(&ports); display_object.add_child(&ports);
ports.add_child(&header); ports.add_child(&header);
@ -268,7 +268,7 @@ impl Model {
fn init(self) -> Self { fn init(self) -> Self {
// FIXME[WD]: Depth sorting of labels to in front of the mouse pointer. Temporary solution. // FIXME[WD]: Depth sorting of labels to in front of the mouse pointer. Temporary solution.
// It needs to be more flexible once we have proper depth management. // It needs to be more flexible once we have proper depth management.
let scene = self.app.display.scene(); let scene = &self.app.display.default_scene;
self.label.remove_from_scene_layer(&scene.layers.main); self.label.remove_from_scene_layer(&scene.layers.main);
self.label.add_to_scene_layer(&scene.layers.label); self.label.add_to_scene_layer(&scene.layers.label);
@ -289,7 +289,7 @@ impl Model {
} }
fn scene(&self) -> &Scene { fn scene(&self) -> &Scene {
self.app.display.scene() &self.app.display.default_scene
} }
/// Run the provided function on the target port if exists. /// Run the provided function on the target port if exists.
@ -614,7 +614,7 @@ impl Area {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for
// shape system (#795) // shape system (#795)
let style_sheet = &self.model.app.display.scene().style_sheet; let style_sheet = &self.model.app.display.default_scene.style_sheet;
let styles = StyleWatch::new(style_sheet); let styles = StyleWatch::new(style_sheet);
let styles_frp = &self.model.styles_frp; let styles_frp = &self.model.styles_frp;
let any_type_sel_color = styles_frp.get_color(theme::code::types::any::selection); let any_type_sel_color = styles_frp.get_color(theme::code::types::any::selection);

View File

@ -175,8 +175,8 @@ impl Model {
let id_crumbs_map = default(); let id_crumbs_map = default();
let expression = default(); let expression = default();
let port_count = default(); let port_count = default();
let styles = StyleWatch::new(&app.display.scene().style_sheet); let styles = StyleWatch::new(&app.display.default_scene.style_sheet);
let styles_frp = StyleWatchFrp::new(&app.display.scene().style_sheet); let styles_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let frp = frp.output.clone_ref(); let frp = frp.output.clone_ref();
display_object.add_child(&label); display_object.add_child(&label);
display_object.add_child(&ports); display_object.add_child(&ports);
@ -199,7 +199,7 @@ impl Model {
fn init(self) -> Self { fn init(self) -> Self {
// FIXME[WD]: Depth sorting of labels to in front of the mouse pointer. Temporary solution. // FIXME[WD]: Depth sorting of labels to in front of the mouse pointer. Temporary solution.
// It needs to be more flexible once we have proper depth management. // It needs to be more flexible once we have proper depth management.
let scene = self.app.display.scene(); let scene = &self.app.display.default_scene;
self.label.remove_from_scene_layer(&scene.layers.main); self.label.remove_from_scene_layer(&scene.layers.main);
self.label.add_to_scene_layer(&scene.layers.label); self.label.add_to_scene_layer(&scene.layers.label);

View File

@ -196,7 +196,7 @@ impl Deref for ProfilingLabel {
impl ProfilingLabel { impl ProfilingLabel {
/// Constructs a `ProfilingLabel` for the given application. /// Constructs a `ProfilingLabel` for the given application.
pub fn new(app: &Application) -> Self { pub fn new(app: &Application) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let root = display::object::Instance::new(Logger::new("ProfilingIndicator")); let root = display::object::Instance::new(Logger::new("ProfilingIndicator"));
let label = text::Area::new(app); let label = text::Area::new(app);

View File

@ -158,7 +158,7 @@ impl StatusIndicator {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795) // system (#795)
let styles = StyleWatch::new(&app.display.scene().style_sheet); let styles = StyleWatch::new(&app.display.default_scene.style_sheet);
frp::extend! { network frp::extend! { network
frp.source.status <+ frp.input.set_status; frp.source.status <+ frp.input.set_status;

View File

@ -143,7 +143,7 @@ impl Deref for Button {
impl Button { impl Button {
/// Constructs a new button for toggling the editor's view mode. /// Constructs a new button for toggling the editor's view mode.
pub fn new(app: &Application) -> Button { pub fn new(app: &Application) -> Button {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let styles = StyleWatchFrp::new(&scene.style_sheet); let styles = StyleWatchFrp::new(&scene.style_sheet);
let frp = Frp::new(); let frp = Frp::new();
let network = &frp.network; let network = &frp.network;

View File

@ -188,7 +188,7 @@ impl Tooltip {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795) // system (#795)
let styles = StyleWatch::new(&app.display.scene().style_sheet); let styles = StyleWatch::new(&app.display.default_scene.style_sheet);
let hide_delay_duration_ms = styles.get_number_or( let hide_delay_duration_ms = styles.get_number_or(
ensogl_hardcoded_theme::application::tooltip::hide_delay_duration_ms, ensogl_hardcoded_theme::application::tooltip::hide_delay_duration_ms,
0.0, 0.0,

View File

@ -31,7 +31,7 @@ use ensogl::display::DomSymbol;
use ensogl::Animation; use ensogl::Animation;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::StyleSetter; use ensogl::system::web::traits::*;
use ensogl_component::shadow; use ensogl_component::shadow;
@ -192,19 +192,19 @@ impl View {
bg_color.alpha bg_color.alpha
); );
let div = web::create_div(); let div = web::document.create_div_or_panic();
let background_dom = DomSymbol::new(&div); let background_dom = DomSymbol::new(&div);
// TODO : We added a HTML background to the `View`, because "shape" background was // TODO : We added a HTML background to the `View`, because "shape" background was
// overlapping the JS visualization. This should be further investigated // overlapping the JS visualization. This should be further investigated
// while fixing rust visualization displaying. (#796) // while fixing rust visualization displaying. (#796)
background_dom.dom().set_style_or_warn("width", "0", &logger); background_dom.dom().set_style_or_warn("width", "0");
background_dom.dom().set_style_or_warn("height", "0", &logger); background_dom.dom().set_style_or_warn("height", "0");
background_dom.dom().set_style_or_warn("z-index", "1", &logger); background_dom.dom().set_style_or_warn("z-index", "1");
background_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); background_dom.dom().set_style_or_warn("overflow-y", "auto");
background_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); background_dom.dom().set_style_or_warn("overflow-x", "auto");
background_dom.dom().set_style_or_warn("background", bg_hex, &logger); background_dom.dom().set_style_or_warn("background", bg_hex);
background_dom.dom().set_style_or_warn("border-radius", "14px", &logger); background_dom.dom().set_style_or_warn("border-radius", "14px");
shadow::add_to_dom_element(&background_dom, &styles, &logger); shadow::add_to_dom_element(&background_dom, &styles);
display_object.add_child(&background_dom); display_object.add_child(&background_dom);
Self { display_object, background, overlay, background_dom, scene }.init() Self { display_object, background, overlay, background_dom, scene }.init()
@ -259,7 +259,7 @@ pub struct ContainerModel {
impl ContainerModel { impl ContainerModel {
/// Constructor. /// Constructor.
pub fn new(logger: &Logger, app: &Application, registry: visualization::Registry) -> Self { pub fn new(logger: &Logger, app: &Application, registry: visualization::Registry) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new_sub(logger, "visualization_container"); let logger = Logger::new_sub(logger, "visualization_container");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let drag_root = display::object::Instance::new(&logger); let drag_root = display::object::Instance::new(&logger);
@ -391,10 +391,10 @@ impl ContainerModel {
// self.fullscreen_view.background.shape.sprite.size.set(size); // self.fullscreen_view.background.shape.sprite.size.set(size);
// self.view.background.shape.sprite.size.set(zero()); // self.view.background.shape.sprite.size.set(zero());
self.view.overlay.size.set(zero()); self.view.overlay.size.set(zero());
dom.set_style_or_warn("width", "0", &self.logger); dom.set_style_or_warn("width", "0");
dom.set_style_or_warn("height", "0", &self.logger); dom.set_style_or_warn("height", "0");
bg_dom.set_style_or_warn("width", format!("{}px", size[0]), &self.logger); bg_dom.set_style_or_warn("width", format!("{}px", size[0]));
bg_dom.set_style_or_warn("height", format!("{}px", size[1]), &self.logger); bg_dom.set_style_or_warn("height", format!("{}px", size[1]));
self.action_bar.frp.set_size.emit(Vector2::zero()); self.action_bar.frp.set_size.emit(Vector2::zero());
} else { } else {
// self.view.background.shape.radius.set(CORNER_RADIUS); // self.view.background.shape.radius.set(CORNER_RADIUS);
@ -402,10 +402,10 @@ impl ContainerModel {
self.view.background.radius.set(CORNER_RADIUS); self.view.background.radius.set(CORNER_RADIUS);
self.view.overlay.size.set(size); self.view.overlay.size.set(size);
self.view.background.size.set(size + 2.0 * Vector2(PADDING, PADDING)); self.view.background.size.set(size + 2.0 * Vector2(PADDING, PADDING));
dom.set_style_or_warn("width", format!("{}px", size[0]), &self.logger); dom.set_style_or_warn("width", format!("{}px", size[0]));
dom.set_style_or_warn("height", format!("{}px", size[1]), &self.logger); dom.set_style_or_warn("height", format!("{}px", size[1]));
bg_dom.set_style_or_warn("width", "0", &self.logger); bg_dom.set_style_or_warn("width", "0");
bg_dom.set_style_or_warn("height", "0", &self.logger); bg_dom.set_style_or_warn("height", "0");
// self.fullscreen_view.background.shape.sprite.size.set(zero()); // self.fullscreen_view.background.shape.sprite.size.set(zero());
let action_bar_size = Vector2::new(size.x, ACTION_BAR_HEIGHT); let action_bar_size = Vector2::new(size.x, ACTION_BAR_HEIGHT);

View File

@ -271,9 +271,9 @@ impl Model {
let icons = Icons::new(logger); let icons = Icons::new(logger);
let shapes = compound::events::MouseEvents::default(); let shapes = compound::events::MouseEvents::default();
app.display.scene().layers.below_main.add_exclusive(&hover_area); app.display.default_scene.layers.below_main.add_exclusive(&hover_area);
app.display.scene().layers.below_main.add_exclusive(&background); app.display.default_scene.layers.below_main.add_exclusive(&background);
app.display.scene().layers.above_nodes.add_exclusive(&icons); app.display.default_scene.layers.above_nodes.add_exclusive(&icons);
shapes.add_sub_shape(&hover_area); shapes.add_sub_shape(&hover_area);
shapes.add_sub_shape(&background); shapes.add_sub_shape(&background);
@ -359,7 +359,7 @@ impl ActionBar {
let network = &self.frp.network; let network = &self.frp.network;
let frp = &self.frp; let frp = &self.frp;
let model = &self.model; let model = &self.model;
let mouse = &app.display.scene().mouse.frp; let mouse = &app.display.default_scene.mouse.frp;
let visualization_chooser = &model.visualization_chooser.frp; let visualization_chooser = &model.visualization_chooser.frp;
frp::extend! { network frp::extend! { network

View File

@ -8,7 +8,7 @@ use ensogl::display::shape::*;
use ensogl::display::traits::*; use ensogl::display::traits::*;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::StyleSetter; use ensogl::system::web::traits::*;
use ensogl_hardcoded_theme as theme; use ensogl_hardcoded_theme as theme;
@ -74,18 +74,18 @@ impl Panel {
let blue = bg_color.blue * 255.0; let blue = bg_color.blue * 255.0;
let bg_hex = format!("rgba({},{},{},{})", red, green, blue, bg_color.alpha); let bg_hex = format!("rgba({},{},{},{})", red, green, blue, bg_color.alpha);
let div = web::create_div(); let div = web::document.create_div_or_panic();
let background_dom = DomSymbol::new(&div); let background_dom = DomSymbol::new(&div);
// TODO : We added a HTML background to the `View`, because "shape" background was // TODO : We added a HTML background to the `View`, because "shape" background was
// overlapping the JS visualization. This should be further investigated // overlapping the JS visualization. This should be further investigated
// while fixing rust visualization displaying. (#796) // while fixing rust visualization displaying. (#796)
background_dom.dom().set_style_or_warn("width", "0", &logger); background_dom.dom().set_style_or_warn("width", "0");
background_dom.dom().set_style_or_warn("height", "0", &logger); background_dom.dom().set_style_or_warn("height", "0");
background_dom.dom().set_style_or_warn("z-index", "1", &logger); background_dom.dom().set_style_or_warn("z-index", "1");
background_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); background_dom.dom().set_style_or_warn("overflow-y", "auto");
background_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); background_dom.dom().set_style_or_warn("overflow-x", "auto");
background_dom.dom().set_style_or_warn("background", bg_hex, &logger); background_dom.dom().set_style_or_warn("background", bg_hex);
background_dom.dom().set_style_or_warn("border-radius", "0", &logger); background_dom.dom().set_style_or_warn("border-radius", "0");
display_object.add_child(&background_dom); display_object.add_child(&background_dom);
scene.dom.layers.fullscreen_vis.manage(&background_dom); scene.dom.layers.fullscreen_vis.manage(&background_dom);

View File

@ -60,7 +60,7 @@ struct Model {
impl Model { impl Model {
pub fn new(app: &Application, registry: visualization::Registry) -> Self { pub fn new(app: &Application, registry: visualization::Registry) -> Self {
let selection_menu = drop_down_menu::DropDownMenu::new(app); let selection_menu = drop_down_menu::DropDownMenu::new(app);
app.display.scene().layers.below_main.add_exclusive(&selection_menu); app.display.default_scene.layers.below_main.add_exclusive(&selection_menu);
Self { selection_menu, registry } Self { selection_menu, registry }
} }

View File

@ -4,17 +4,21 @@ use crate::prelude::*;
use crate::component::type_coloring; use crate::component::type_coloring;
use crate::component::visualization::foreign::java_script::PreprocessorCallback; use crate::component::visualization::foreign::java_script::PreprocessorCallback;
use crate::component::visualization::instance::PreprocessorConfiguration;
use crate::Type; use crate::Type;
use ensogl::data::color; use ensogl::data::color;
use ensogl::display::shape::StyleWatch; use ensogl::display::shape::StyleWatch;
use ensogl::display::style::data::DataMatch; use ensogl::display::style::data::DataMatch;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::system::web::HtmlDivElement;
use ensogl::system::web::JsValue;
use ensogl_hardcoded_theme; use ensogl_hardcoded_theme;
use fmt::Formatter; use fmt::Formatter;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::wasm_bindgen;
use web_sys::HtmlDivElement;
#[cfg(target_arch = "wasm32")]
use crate::component::visualization::instance::PreprocessorConfiguration;
// ================= // =================
@ -30,6 +34,10 @@ pub const JS_CLASS_NAME: &str = "Visualization";
// === JavaScript Bindings === // === JavaScript Bindings ===
// =========================== // ===========================
// TODO: Add docs to the WASM32 bindings below - esp. what is the difference between
// __Visualization__ and Visualization.
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(module = "/src/component/visualization/foreign/java_script/visualization.js")] #[wasm_bindgen(module = "/src/component/visualization/foreign/java_script/visualization.js")]
extern "C" { extern "C" {
#[allow(unsafe_code)] #[allow(unsafe_code)]
@ -48,8 +56,32 @@ extern "C" {
pub fn emitPreprocessorChange(this: &Visualization) -> Result<(), JsValue>; pub fn emitPreprocessorChange(this: &Visualization) -> Result<(), JsValue>;
} }
/// Provides reference to the visualizations JavaScript base class. #[allow(non_snake_case)]
pub fn js_class() -> JsValue { #[cfg(not(target_arch = "wasm32"))]
fn __Visualization__() -> JsValue {
default()
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Copy, Clone, Debug, Default)]
#[allow(missing_docs)]
pub struct Visualization {}
#[cfg(not(target_arch = "wasm32"))]
#[allow(missing_docs)]
impl Visualization {
pub fn new() -> Self {
default()
}
#[allow(non_snake_case)]
pub fn emitPreprocessorChange(&self) -> Result<(), JsValue> {
Ok(())
}
}
/// Provides reference to the [`Visualization`] JavaScript base class.
pub fn js_visualization_class() -> JsValue {
__Visualization__() __Visualization__()
} }
@ -59,7 +91,7 @@ pub fn js_class() -> JsValue {
// === Theme === // === Theme ===
// ============= // =============
/// The theming API that we expose to JS visualizations /// The theming API that we expose to JS visualizations.
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct JsTheme { pub struct JsTheme {
@ -123,6 +155,7 @@ impl JsTheme {
/// Data that is passed into the javascript Visualization baseclass. /// Data that is passed into the javascript Visualization baseclass.
#[allow(missing_docs)] #[allow(missing_docs)]
#[wasm_bindgen] #[wasm_bindgen]
#[allow(dead_code)] // Some fields are used by wasm32 target only.
pub struct JsConsArgs { pub struct JsConsArgs {
#[wasm_bindgen(skip)] #[wasm_bindgen(skip)]
pub root: HtmlDivElement, pub root: HtmlDivElement,
@ -151,6 +184,7 @@ impl JsConsArgs {
} }
} }
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen] #[wasm_bindgen]
impl JsConsArgs { impl JsConsArgs {
/// Getter for the root element for the visualization. /// Getter for the root element for the visualization.

View File

@ -2,11 +2,7 @@
//! //!
//! For details of the API please see: //! For details of the API please see:
//! * docstrings in the `visualization.js` file; //! * docstrings in the `visualization.js` file;
//! * [visualization documentation](https://dev.enso.org/docs/ide/product/visualizations.html). //! * [visualization documentation](https://enso.org/docs/developer/ide/product/visualizations.html).
// FIXME: Can we simplify the above definition so its more minimal, yet functional?
mod function;
use function::Function;
use crate::prelude::*; use crate::prelude::*;
@ -18,12 +14,13 @@ use crate::component::visualization::InstantiationResult;
use crate::visualization::foreign::java_script::Sources; use crate::visualization::foreign::java_script::Sources;
use ensogl::display::Scene; use ensogl::display::Scene;
use ensogl::system::web;
use ensogl::system::web::traits::*;
use ensogl::system::web::Function;
use ensogl::system::web::JsString;
use ensogl::system::web::JsValue; use ensogl::system::web::JsValue;
use fmt::Formatter; use fmt::Formatter;
use js_sys;
use js_sys::JsString;
use std::str::FromStr; use std::str::FromStr;
use wasm_bindgen::JsCast;
@ -63,16 +60,13 @@ impl Definition {
pub fn new(project: visualization::path::Project, sources: Sources) -> Result<Self, Error> { pub fn new(project: visualization::path::Project, sources: Sources) -> Result<Self, Error> {
let source = sources.to_string(&project); let source = sources.to_string(&project);
let context = JsValue::NULL; let context = JsValue::NULL;
let function = Function::new_with_args(binding::JS_CLASS_NAME, &source) let function = Function::new_with_args_fixed(binding::JS_CLASS_NAME, &source)
.map_err(Error::InvalidFunction)?; .map_err(Error::InvalidFunction)?;
let js_class = binding::js_class(); let js_class = binding::js_visualization_class();
let class = function.call1(&context, &js_class).map_err(Error::InvalidFunction)?; let class = function.call1(&context, &js_class).map_err(Error::InvalidFunction)?;
let input_type = try_str_field(&class, field::INPUT_TYPE).unwrap_or_default(); let input_type = try_str_field(&class, field::INPUT_TYPE).unwrap_or_default();
let input_format = try_str_field(&class, field::INPUT_FORMAT).unwrap_or_default(); let input_format = try_str_field(&class, field::INPUT_FORMAT).unwrap_or_default();
let input_format = visualization::data::Format::from_str(&input_format).unwrap_or_default(); let input_format = visualization::data::Format::from_str(&input_format).unwrap_or_default();
let label = label(&class)?; let label = label(&class)?;
let path = visualization::Path::new(project, label); let path = visualization::Path::new(project, label);
let signature = visualization::Signature::new(path, input_type, input_format); let signature = visualization::Signature::new(path, input_type, input_format);
@ -102,7 +96,7 @@ impl From<Definition> for visualization::Definition {
// === Utils === // === Utils ===
fn try_str_field(obj: &JsValue, field: &str) -> Option<String> { fn try_str_field(obj: &JsValue, field: &str) -> Option<String> {
let field = js_sys::Reflect::get(obj, &field.into()).ok()?; let field = web::Reflect::get(obj, &field.into()).ok()?;
let js_string = field.dyn_ref::<JsString>()?; let js_string = field.dyn_ref::<JsString>()?;
Some(js_string.into()) Some(js_string.into())
} }

View File

@ -1,32 +0,0 @@
//! The whole content of this file consists of parts that were copied from `js_sys` just to modify
//! `new_with_args` such that it catches syntax errors. This is supposed to be temporary solution.
//! I opened a GitHub issue suggesting that this will be included in wasm-bindgen
//! (https://github.com/rustwasm/wasm-bindgen/issues/2496).
//! A PR with a suggested change to wasm-bindgen can be found here:
//! https://github.com/rustwasm/wasm-bindgen/pull/2497
use js_sys::*;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Object, is_type_of = JsValue::is_function)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type Function;
/// The `Function` constructor creates a new `Function` object. Calling the
/// constructor directly can create functions dynamically, but suffers from
/// security and similar (but far less significant) performance issues
/// similar to `eval`. However, unlike `eval`, the `Function` constructor
/// allows executing code in the global scope, prompting better programming
/// habits and allowing for more efficient code minification.
#[allow(unsafe_code)]
#[wasm_bindgen(constructor, catch)]
pub fn new_with_args(args: &str, body: &str) -> Result<Function, JsValue>;
/// The `call()` method calls a function with a given this value and
/// arguments provided individually.
#[allow(unsafe_code)]
#[wasm_bindgen(method, catch, js_name = call)]
pub fn call1(this: &Function, context: &JsValue, arg1: &JsValue) -> Result<JsValue, JsValue>;
}

View File

@ -11,6 +11,7 @@ use crate::prelude::*;
use crate::component::visualization; use crate::component::visualization;
use crate::component::visualization::instance::PreprocessorConfiguration; use crate::component::visualization::instance::PreprocessorConfiguration;
use crate::component::visualization::java_script;
use crate::component::visualization::java_script::binding::JsConsArgs; use crate::component::visualization::java_script::binding::JsConsArgs;
use crate::component::visualization::java_script::method; use crate::component::visualization::java_script::method;
use crate::component::visualization::*; use crate::component::visualization::*;
@ -24,9 +25,8 @@ use ensogl::display::DomScene;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::display::Scene; use ensogl::display::Scene;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::traits::*;
use ensogl::system::web::JsValue; use ensogl::system::web::JsValue;
use ensogl::system::web::StyleSetter;
use js_sys;
use std::fmt::Formatter; use std::fmt::Formatter;
@ -89,8 +89,8 @@ type PreprocessorCallbackCell = Rc<RefCell<Option<Box<dyn PreprocessorCallback>>
pub struct InstanceModel { pub struct InstanceModel {
pub root_node: DomSymbol, pub root_node: DomSymbol,
pub logger: Logger, pub logger: Logger,
on_data_received: Rc<Option<js_sys::Function>>, on_data_received: Rc<Option<web::Function>>,
set_size: Rc<Option<js_sys::Function>>, set_size: Rc<Option<web::Function>>,
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
object: Rc<java_script::binding::Visualization>, object: Rc<java_script::binding::Visualization>,
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
@ -104,8 +104,8 @@ impl InstanceModel {
styles.get_color(ensogl_hardcoded_theme::graph_editor::visualization::background) styles.get_color(ensogl_hardcoded_theme::graph_editor::visualization::background)
} }
fn create_root(scene: &Scene, logger: &Logger) -> result::Result<DomSymbol, Error> { fn create_root(scene: &Scene) -> result::Result<DomSymbol, Error> {
let div = web::create_div(); let div = web::document.create_div_or_panic();
let root_node = DomSymbol::new(&div); let root_node = DomSymbol::new(&div);
root_node root_node
.dom() .dom()
@ -117,7 +117,7 @@ impl InstanceModel {
let bg_green = bg_color.green * 255.0; let bg_green = bg_color.green * 255.0;
let bg_blue = bg_color.blue * 255.0; let bg_blue = bg_color.blue * 255.0;
let bg_hex = format!("rgba({},{},{},{})", bg_red, bg_green, bg_blue, bg_color.alpha); let bg_hex = format!("rgba({},{},{},{})", bg_red, bg_green, bg_blue, bg_color.alpha);
root_node.dom().set_style_or_warn("background", bg_hex, logger); root_node.dom().set_style_or_warn("background", bg_hex);
Ok(root_node) Ok(root_node)
} }
@ -140,11 +140,12 @@ impl InstanceModel {
(closure_cell, closure) (closure_cell, closure)
} }
#[cfg(target_arch = "wasm32")]
fn instantiate_class_with_args( fn instantiate_class_with_args(
class: &JsValue, class: &JsValue,
args: JsConsArgs, args: JsConsArgs,
) -> result::Result<java_script::binding::Visualization, Error> { ) -> result::Result<java_script::binding::Visualization, Error> {
let js_new = js_sys::Function::new_with_args("cls,arg", "return new cls(arg)"); let js_new = web::Function::new_with_args_fixed("cls,arg", "return new cls(arg)").unwrap();
let context = JsValue::NULL; let context = JsValue::NULL;
let object = js_new let object = js_new
.call2(&context, class, &args.into()) .call2(&context, class, &args.into())
@ -156,17 +157,25 @@ impl InstanceModel {
Ok(object) Ok(object)
} }
#[cfg(not(target_arch = "wasm32"))]
fn instantiate_class_with_args(
_class: &JsValue,
_args: JsConsArgs,
) -> result::Result<java_script::binding::Visualization, Error> {
Ok(java_script::binding::Visualization::new())
}
/// Tries to create a InstanceModel from the given visualisation class. /// Tries to create a InstanceModel from the given visualisation class.
pub fn from_class(class: &JsValue, scene: &Scene) -> result::Result<Self, Error> { pub fn from_class(class: &JsValue, scene: &Scene) -> result::Result<Self, Error> {
let logger = Logger::new("Instance"); let logger = Logger::new("Instance");
let root_node = Self::create_root(scene, &logger)?; let root_node = Self::create_root(scene)?;
let (preprocessor_change, closure) = Self::preprocessor_change_callback(); let (preprocessor_change, closure) = Self::preprocessor_change_callback();
let styles = StyleWatch::new(&scene.style_sheet); let styles = StyleWatch::new(&scene.style_sheet);
let init_data = JsConsArgs::new(root_node.clone_ref(), styles, closure); let init_data = JsConsArgs::new(root_node.clone_ref(), styles, closure);
let object = Self::instantiate_class_with_args(class, init_data)?; let object = Self::instantiate_class_with_args(class, init_data)?;
let on_data_received = get_method(object.as_ref(), method::ON_DATA_RECEIVED).ok(); let on_data_received = get_method(&object, method::ON_DATA_RECEIVED).ok();
let on_data_received = Rc::new(on_data_received); let on_data_received = Rc::new(on_data_received);
let set_size = get_method(object.as_ref(), method::SET_SIZE).ok(); let set_size = get_method(&object, method::SET_SIZE).ok();
let set_size = Rc::new(set_size); let set_size = Rc::new(set_size);
let object = Rc::new(object); let object = Rc::new(object);
let scene = scene.clone_ref(); let scene = scene.clone_ref();
@ -188,12 +197,17 @@ impl InstanceModel {
scene.manage(&self.root_node); scene.manage(&self.root_node);
} }
#[cfg(target_arch = "wasm32")]
fn set_size(&self, size: Vector2) { fn set_size(&self, size: Vector2) {
let data_json = JsValue::from_serde(&size).unwrap(); let data_json = JsValue::from_serde(&size).unwrap();
let _ = self.try_call1(&self.set_size, &data_json); let _ = self.try_call1(&self.set_size, &data_json);
self.root_node.set_size(size); self.root_node.set_size(size);
} }
#[cfg(not(target_arch = "wasm32"))]
fn set_size(&self, _size: Vector2) {}
#[cfg(target_arch = "wasm32")]
fn receive_data(&self, data: &Data) -> result::Result<(), DataError> { fn receive_data(&self, data: &Data) -> result::Result<(), DataError> {
let data_json = match data { let data_json = match data {
Data::Json { content } => content, Data::Json { content } => content,
@ -209,15 +223,21 @@ impl InstanceModel {
Ok(()) Ok(())
} }
#[cfg(not(target_arch = "wasm32"))]
fn receive_data(&self, _data: &Data) -> result::Result<(), DataError> {
Ok(())
}
/// Prompt visualization JS object to emit preprocessor change with its currently desired state. /// Prompt visualization JS object to emit preprocessor change with its currently desired state.
pub fn update_preprocessor(&self) -> result::Result<(), JsValue> { pub fn update_preprocessor(&self) -> result::Result<(), JsValue> {
self.object.emitPreprocessorChange() self.object.emitPreprocessorChange()
} }
#[cfg(target_arch = "wasm32")]
/// Helper method to call methods on the wrapped javascript object. /// Helper method to call methods on the wrapped javascript object.
fn try_call1( fn try_call1(
&self, &self,
method: &Option<js_sys::Function>, method: &Option<web::Function>,
arg: &JsValue, arg: &JsValue,
) -> result::Result<(), JsValue> { ) -> result::Result<(), JsValue> {
if let Some(method) = method { if let Some(method) = method {
@ -283,13 +303,11 @@ impl Instance {
let callback = move |preprocessor_config| change.emit(preprocessor_config); let callback = move |preprocessor_config| change.emit(preprocessor_config);
let callback = Box::new(callback); let callback = Box::new(callback);
self.model.preprocessor_change.borrow_mut().replace(callback); self.model.preprocessor_change.borrow_mut().replace(callback);
if let Err(js_error) = self.model.update_preprocessor() { if let Err(err) = self.model.update_preprocessor() {
use enso_frp::web::js_to_string;
let logger = self.model.logger.clone(); let logger = self.model.logger.clone();
error!( error!(
logger, logger,
"Failed to trigger initial preprocessor update from JS: \ "Failed to trigger initial preprocessor update from JS: {err.print_to_string()}"
{js_to_string(&js_error)}"
); );
} }
self self
@ -311,10 +329,11 @@ impl display::Object for Instance {
// === Utils === // === Utils ===
#[cfg(target_arch = "wasm32")]
/// Try to return the method specified by the given name on the given object as a /// Try to return the method specified by the given name on the given object as a
/// `js_sys::Function`. /// `web::Function`.
fn get_method(object: &js_sys::Object, property: &str) -> Result<js_sys::Function> { fn get_method(object: &web::Object, property: &str) -> Result<web::Function> {
let method_value = js_sys::Reflect::get(object, &property.into()); let method_value = web::Reflect::get(object, &property.into());
let method_value = method_value.map_err(|object| Error::PropertyNotFoundOnObject { let method_value = method_value.map_err(|object| Error::PropertyNotFoundOnObject {
object, object,
property: property.to_string(), property: property.to_string(),
@ -323,6 +342,14 @@ fn get_method(object: &js_sys::Object, property: &str) -> Result<js_sys::Functio
let object: JsValue = object.into(); let object: JsValue = object.into();
return Err(Error::PropertyNotFoundOnObject { object, property: property.to_string() }); return Err(Error::PropertyNotFoundOnObject { object, property: property.to_string() });
} }
let method_function: js_sys::Function = method_value.into(); let method_function: web::Function = method_value.into();
Ok(method_function) Ok(method_function)
} }
#[cfg(not(target_arch = "wasm32"))]
fn get_method(
_object: &java_script::binding::Visualization,
_property: &str,
) -> Result<web::Function> {
Ok(default())
}

View File

@ -63,6 +63,7 @@ use ensogl::display::Scene;
use ensogl::gui::cursor; use ensogl::gui::cursor;
use ensogl::prelude::*; use ensogl::prelude::*;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::traits::*;
use ensogl::Animation; use ensogl::Animation;
use ensogl::DEPRECATED_Animation; use ensogl::DEPRECATED_Animation;
use ensogl::DEPRECATED_Tween; use ensogl::DEPRECATED_Tween;
@ -1623,7 +1624,7 @@ impl GraphEditorModel {
#[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs, always. #[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs, always.
pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self { pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self {
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("GraphEditor"); let logger = Logger::new("GraphEditor");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let nodes = Nodes::new(&logger); let nodes = Nodes::new(&logger);
@ -1687,7 +1688,7 @@ impl GraphEditorModel {
} }
fn scene(&self) -> &Scene { fn scene(&self) -> &Scene {
self.app.display.scene() &self.app.display.default_scene
} }
} }
@ -2479,7 +2480,7 @@ pub fn enable_disable_toggle(
#[allow(unused_parens)] #[allow(unused_parens)]
fn new_graph_editor(app: &Application) -> GraphEditor { fn new_graph_editor(app: &Application) -> GraphEditor {
let world = &app.display; let world = &app.display;
let scene = world.scene(); let scene = &world.default_scene;
let cursor = &app.cursor; let cursor = &app.cursor;
let frp = Frp::new(); let frp = Frp::new();
let model = GraphEditorModelWithNetwork::new(app, cursor.clone_ref(), &frp); let model = GraphEditorModelWithNetwork::new(app, cursor.clone_ref(), &frp);
@ -3263,8 +3264,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
viz_was_pressed <- viz_pressed.previous(); viz_was_pressed <- viz_pressed.previous();
viz_press <- viz_press_ev.gate_not(&viz_was_pressed); viz_press <- viz_press_ev.gate_not(&viz_was_pressed);
viz_release <- viz_release_ev.gate(&viz_was_pressed); viz_release <- viz_release_ev.gate(&viz_was_pressed);
viz_press_time <- viz_press . map(|_| web::performance().now() as f32); viz_press_time <- viz_press . map(|_| web::window.performance_or_panic().now() as f32);
viz_release_time <- viz_release . map(|_| web::performance().now() as f32); viz_release_time <- viz_release . map(|_| web::window.performance_or_panic().now() as f32);
viz_press_time_diff <- viz_release_time.map2(&viz_press_time,|t1,t0| t1-t0); viz_press_time_diff <- viz_release_time.map2(&viz_press_time,|t1,t0| t1-t0);
viz_preview_mode <- viz_press_time_diff.map(|t| *t > VIZ_PREVIEW_MODE_TOGGLE_TIME_MS); viz_preview_mode <- viz_press_time_diff.map(|t| *t > VIZ_PREVIEW_MODE_TOGGLE_TIME_MS);
viz_preview_mode_end <- viz_release.gate(&viz_preview_mode).gate_not(&out.is_fs_visualization_displayed); viz_preview_mode_end <- viz_release.gate(&viz_preview_mode).gate_not(&out.is_fs_visualization_displayed);

View File

@ -67,8 +67,8 @@ impl Deref for View {
impl View { impl View {
/// Create Code Editor component. /// Create Code Editor component.
pub fn new(app: &Application) -> Self { pub fn new(app: &Application) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let styles = StyleWatchFrp::new(&app.display.scene().style_sheet); let styles = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let frp = Frp::new(); let frp = Frp::new();
let network = &frp.network; let network = &frp.network;
let model = app.new_view::<text::Area>(); let model = app.new_view::<text::Area>();
@ -101,7 +101,7 @@ impl View {
frp.source.is_visible <+ frp.toggle.map2(&is_visible, |(),b| !b); frp.source.is_visible <+ frp.toggle.map2(&is_visible, |(),b| !b);
def init = source_(); def init = source_();
let shape = app.display.scene().shape(); let shape = app.display.default_scene.shape();
position <- all_with3(&height_fraction.value,shape,&init, |height_f,scene_size,_init| { position <- all_with3(&height_fraction.value,shape,&init, |height_f,scene_size,_init| {
let height = height_f * scene_size.height; let height = height_f * scene_size.height;
let x = -scene_size.width / 2.0 + PADDING_LEFT; let x = -scene_size.width / 2.0 + PADDING_LEFT;

View File

@ -61,8 +61,8 @@ impl PopupLabel {
let network = frp::Network::new("PopupLabel"); let network = frp::Network::new("PopupLabel");
let label = Label::new(app); let label = Label::new(app);
label.set_opacity(0.0); label.set_opacity(0.0);
let background_layer = &app.display.scene().layers.panel; let background_layer = &app.display.default_scene.layers.panel;
let text_layer = &app.display.scene().layers.panel_text; let text_layer = &app.display.default_scene.layers.panel_text;
label.set_layers(background_layer, text_layer); label.set_layers(background_layer, text_layer);
let opacity_animation = Animation::new(&network); let opacity_animation = Animation::new(&network);
@ -175,7 +175,7 @@ impl View {
frp::extend! { network frp::extend! { network
init <- source_(); init <- source_();
let shape = app.display.scene().shape(); let shape = app.display.default_scene.shape();
_eval <- all_with(shape, &init, f!([model](scene_size, _init) { _eval <- all_with(shape, &init, f!([model](scene_size, _init) {
let half_height = scene_size.height / 2.0; let half_height = scene_size.height / 2.0;
let label_height = model.label_height(); let label_height = model.label_height();

View File

@ -15,13 +15,12 @@ use ensogl::display::shape::primitive::StyleWatch;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::clipboard; use ensogl::system::web::clipboard;
use ensogl::system::web::AttributeSetter; use ensogl::system::web::traits::*;
use ensogl::system::web::StyleSetter;
use ensogl_component::shadow; use ensogl_component::shadow;
use wasm_bindgen::closure::Closure; use web::Closure;
use wasm_bindgen::JsCast; use web::HtmlElement;
use web_sys::HtmlElement; use web::JsCast;
use web_sys::MouseEvent; use web::MouseEvent;
@ -70,9 +69,9 @@ impl Model {
fn new(scene: &Scene) -> Self { fn new(scene: &Scene) -> Self {
let logger = Logger::new("DocumentationView"); let logger = Logger::new("DocumentationView");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let outer_div = web::create_div(); let outer_div = web::document.create_div_or_panic();
let outer_dom = DomSymbol::new(&outer_div); let outer_dom = DomSymbol::new(&outer_div);
let inner_div = web::create_div(); let inner_div = web::document.create_div_or_panic();
let inner_dom = DomSymbol::new(&inner_div); let inner_dom = DomSymbol::new(&inner_div);
let size = let size =
Rc::new(Cell::new(Vector2(VIEW_WIDTH - PADDING, VIEW_HEIGHT - PADDING - PADDING_TOP))); Rc::new(Cell::new(Vector2(VIEW_WIDTH - PADDING, VIEW_HEIGHT - PADDING - PADDING_TOP)));
@ -85,22 +84,22 @@ impl Model {
let bg_color = styles.get_color(style_path); let bg_color = styles.get_color(style_path);
let bg_color = bg_color.to_javascript_string(); let bg_color = bg_color.to_javascript_string();
outer_dom.dom().set_style_or_warn("white-space", "normal", &logger); outer_dom.dom().set_style_or_warn("white-space", "normal");
outer_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); outer_dom.dom().set_style_or_warn("overflow-y", "auto");
outer_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); outer_dom.dom().set_style_or_warn("overflow-x", "auto");
outer_dom.dom().set_style_or_warn("background-color", bg_color, &logger); outer_dom.dom().set_style_or_warn("background-color", bg_color);
outer_dom.dom().set_style_or_warn("pointer-events", "auto", &logger); outer_dom.dom().set_style_or_warn("pointer-events", "auto");
outer_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS), &logger); outer_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS));
shadow::add_to_dom_element(&outer_dom, &styles, &logger); shadow::add_to_dom_element(&outer_dom, &styles);
inner_dom.dom().set_attribute_or_warn("class", "scrollable", &logger); inner_dom.dom().set_attribute_or_warn("class", "scrollable");
inner_dom.dom().set_style_or_warn("white-space", "normal", &logger); inner_dom.dom().set_style_or_warn("white-space", "normal");
inner_dom.dom().set_style_or_warn("overflow-y", "auto", &logger); inner_dom.dom().set_style_or_warn("overflow-y", "auto");
inner_dom.dom().set_style_or_warn("overflow-x", "auto", &logger); inner_dom.dom().set_style_or_warn("overflow-x", "auto");
inner_dom.dom().set_style_or_warn("padding", format!("{}px", PADDING), &logger); inner_dom.dom().set_style_or_warn("padding", format!("{}px", PADDING));
inner_dom.dom().set_style_or_warn("padding-top", "5px", &logger); inner_dom.dom().set_style_or_warn("padding-top", "5px");
inner_dom.dom().set_style_or_warn("pointer-events", "auto", &logger); inner_dom.dom().set_style_or_warn("pointer-events", "auto");
inner_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS), &logger); inner_dom.dom().set_style_or_warn("border-radius", format!("{}px", CORNER_RADIUS));
overlay.roundness.set(1.0); overlay.roundness.set(1.0);
overlay.radius.set(CORNER_RADIUS); overlay.radius.set(CORNER_RADIUS);
@ -151,12 +150,11 @@ impl Model {
let create_closures = || -> Option<CodeCopyClosure> { let create_closures = || -> Option<CodeCopyClosure> {
let copy_button = copy_buttons.get_with_index(i)?.dyn_into::<HtmlElement>().ok()?; let copy_button = copy_buttons.get_with_index(i)?.dyn_into::<HtmlElement>().ok()?;
let code_block = code_blocks.get_with_index(i)?.dyn_into::<HtmlElement>().ok()?; let code_block = code_blocks.get_with_index(i)?.dyn_into::<HtmlElement>().ok()?;
let closure = Box::new(move |_event: MouseEvent| { let closure: Closure<dyn FnMut(MouseEvent)> = Closure::new(move |_: MouseEvent| {
let inner_code = code_block.inner_text(); let inner_code = code_block.inner_text();
clipboard::write_text(inner_code); clipboard::write_text(inner_code);
}); });
let closure: Closure<dyn FnMut(MouseEvent)> = Closure::wrap(closure); let callback = closure.as_js_function();
let callback = closure.as_ref().unchecked_ref();
match copy_button.add_event_listener_with_callback("click", callback) { match copy_button.add_event_listener_with_callback("click", callback) {
Ok(_) => Some(closure), Ok(_) => Some(closure),
Err(e) => { Err(e) => {
@ -219,12 +217,8 @@ impl Model {
let padding = (size.x.min(size.y) / 2.0).min(PADDING); let padding = (size.x.min(size.y) / 2.0).min(PADDING);
self.outer_dom.set_size(Vector2(size.x, size.y)); self.outer_dom.set_size(Vector2(size.x, size.y));
self.inner_dom.set_size(Vector2(size.x - padding, size.y - padding - PADDING_TOP)); self.inner_dom.set_size(Vector2(size.x - padding, size.y - padding - PADDING_TOP));
self.inner_dom.dom().set_style_or_warn("padding", format!("{}px", padding), &self.logger); self.inner_dom.dom().set_style_or_warn("padding", format!("{}px", padding));
self.inner_dom.dom().set_style_or_warn( self.inner_dom.dom().set_style_or_warn("padding-top", format!("{}px", PADDING_TOP));
"padding-top",
format!("{}px", PADDING_TOP),
&self.logger,
);
} }
} }

View File

@ -37,7 +37,7 @@ impl OpenDialog {
pub fn new(app: &Application) -> Self { pub fn new(app: &Application) -> Self {
let logger = Logger::new("OpenDialog"); let logger = Logger::new("OpenDialog");
let network = frp::Network::new("OpenDialog"); let network = frp::Network::new("OpenDialog");
let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet); let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let project_list = project_list::ProjectList::new(app); let project_list = project_list::ProjectList::new(app);
let file_browser = FileBrowser::new(); let file_browser = FileBrowser::new();
// Once FileBrowser will be implemented as component, it should be instantiated this way: // Once FileBrowser will be implemented as component, it should be instantiated this way:
@ -47,7 +47,7 @@ impl OpenDialog {
display_object.add_child(&project_list); display_object.add_child(&project_list);
display_object.add_child(&file_browser); display_object.add_child(&file_browser);
app.display.scene().layers.panel.add_exclusive(&display_object); app.display.default_scene.layers.panel.add_exclusive(&display_object);
use theme::application as theme_app; use theme::application as theme_app;
let project_list_width = style_watch.get_number(theme_app::project_list::width); let project_list_width = style_watch.get_number(theme_app::project_list::width);

View File

@ -94,19 +94,19 @@ impl ProjectList {
display_object.add_child(&background); display_object.add_child(&background);
display_object.add_child(&caption); display_object.add_child(&caption);
display_object.add_child(&list); display_object.add_child(&list);
app.display.scene().layers.panel.add_exclusive(&display_object); app.display.default_scene.layers.panel.add_exclusive(&display_object);
caption.set_content("Open Project"); caption.set_content("Open Project");
caption.add_to_scene_layer(&app.display.scene().layers.panel_text); caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text);
list.set_label_layer(app.display.scene().layers.panel_text.id()); list.set_label_layer(app.display.default_scene.layers.panel_text.id());
ensogl::shapes_order_dependencies! { ensogl::shapes_order_dependencies! {
app.display.scene() => { app.display.default_scene => {
background -> list_view::selection; background -> list_view::selection;
list_view::background -> background; list_view::background -> background;
} }
} }
let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet); let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let width = style_watch.get_number(theme::width); let width = style_watch.get_number(theme::width);
let height = style_watch.get_number(theme::height); let height = style_watch.get_number(theme::height);
let bar_height = style_watch.get_number(theme::bar::height); let bar_height = style_watch.get_number(theme::bar::height);

View File

@ -22,6 +22,7 @@ use ensogl::display;
use ensogl::display::shape::*; use ensogl::display::shape::*;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::dom; use ensogl::system::web::dom;
use ensogl::system::web::traits::*;
use ensogl::Animation; use ensogl::Animation;
use ensogl::DEPRECATED_Animation; use ensogl::DEPRECATED_Animation;
use ensogl_hardcoded_theme::Theme; use ensogl_hardcoded_theme::Theme;
@ -145,7 +146,7 @@ struct Model {
impl Model { impl Model {
fn new(app: &Application) -> Self { fn new(app: &Application) -> Self {
let logger = Logger::new("project::View"); let logger = Logger::new("project::View");
let scene = app.display.scene(); let scene = &app.display.default_scene;
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let searcher = app.new_view::<searcher::View>(); let searcher = app.new_view::<searcher::View>();
let graph_editor = app.new_view::<GraphEditor>(); let graph_editor = app.new_view::<GraphEditor>();
@ -213,7 +214,7 @@ impl Model {
} }
fn set_html_style(&self, style: &'static str) { fn set_html_style(&self, style: &'static str) {
web::with_element_by_id_or_warn(&self.logger, "root", |root| root.set_class_name(style)); web::document.with_element_by_id_or_warn("root", |root| root.set_class_name(style));
} }
fn searcher_left_top_position_when_under_node_at(position: Vector2<f32>) -> Vector2<f32> { fn searcher_left_top_position_when_under_node_at(position: Vector2<f32>) -> Vector2<f32> {
@ -369,7 +370,7 @@ impl View {
display::style::javascript::expose_to_window(&app.themes); display::style::javascript::expose_to_window(&app.themes);
let scene = app.display.scene().clone_ref(); let scene = app.display.default_scene.clone_ref();
let model = Model::new(app); let model = Model::new(app);
let frp = Frp::new(); let frp = Frp::new();
let searcher = &model.searcher.frp; let searcher = &model.searcher.frp;

View File

@ -113,7 +113,7 @@ struct Model {
impl Model { impl Model {
fn new(app: &Application) -> Self { fn new(app: &Application) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let app = app.clone_ref(); let app = app.clone_ref();
let logger = Logger::new("SearcherView"); let logger = Logger::new("SearcherView");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
@ -126,7 +126,7 @@ impl Model {
// FIXME: StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME: StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795) // system (#795)
let style = StyleWatch::new(&app.display.scene().style_sheet); let style = StyleWatch::new(&app.display.default_scene.style_sheet);
let action_list_gap_path = ensogl_hardcoded_theme::application::searcher::action_list_gap; let action_list_gap_path = ensogl_hardcoded_theme::application::searcher::action_list_gap;
let action_list_gap = style.get_number_or(action_list_gap_path, 0.0); let action_list_gap = style.get_number_or(action_list_gap_path, 0.0);
list.set_label_layer(scene.layers.above_nodes_text.id()); list.set_label_layer(scene.layers.above_nodes_text.id());

View File

@ -10,7 +10,7 @@ use ensogl::display::shape::compound::path::path;
use ensogl::display::shape::*; use ensogl::display::shape::*;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::StyleSetter; use ensogl::system::web::traits::*;
use ensogl_hardcoded_theme::application::searcher::icons as theme; use ensogl_hardcoded_theme::application::searcher::icons as theme;
use std::f32::consts::PI; use std::f32::consts::PI;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -998,27 +998,24 @@ mod frame {
#[wasm_bindgen] #[wasm_bindgen]
#[allow(dead_code)] #[allow(dead_code)]
pub fn entry_point_searcher_icons() { pub fn entry_point_searcher_icons() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
let logger = Logger::new("Icons example"); let logger = Logger::new("Icons example");
let app = Application::new(&web::get_html_element_by_id("root").unwrap()); let app = Application::new("root");
ensogl_hardcoded_theme::builtin::dark::register(&app); ensogl_hardcoded_theme::builtin::dark::register(&app);
ensogl_hardcoded_theme::builtin::light::register(&app); ensogl_hardcoded_theme::builtin::light::register(&app);
ensogl_hardcoded_theme::builtin::light::enable(&app); ensogl_hardcoded_theme::builtin::light::enable(&app);
let world = app.display.clone(); let world = app.display.clone();
mem::forget(app); mem::forget(app);
let scene = world.scene(); let scene = &world.default_scene;
mem::forget(Navigator::new(scene, &scene.camera())); mem::forget(Navigator::new(scene, &scene.camera()));
// === Grid === // === Grid ===
let grid_div = web::create_div(); let grid_div = web::document.create_div_or_panic();
grid_div.set_style_or_panic("width", "1000px"); grid_div.set_style_or_warn("width", "1000px");
grid_div.set_style_or_panic("height", "16px"); grid_div.set_style_or_warn("height", "16px");
grid_div.set_style_or_panic("background-size", "1.0px 1.0px"); grid_div.set_style_or_warn("background-size", "1.0px 1.0px");
grid_div.set_style_or_panic( grid_div.set_style_or_warn(
"background-image", "background-image",
"linear-gradient(to right, grey 0.05px, transparent 0.05px), "linear-gradient(to right, grey 0.05px, transparent 0.05px),
linear-gradient(to bottom, grey 0.05px, transparent 0.05px)", linear-gradient(to bottom, grey 0.05px, transparent 0.05px)",

View File

@ -158,7 +158,7 @@ struct Model {
impl Model { impl Model {
fn new(app: &Application) -> Self { fn new(app: &Application) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("StatusBar"); let logger = Logger::new("StatusBar");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let root = display::object::Instance::new(&logger); let root = display::object::Instance::new(&logger);
@ -174,7 +174,7 @@ impl Model {
label.add_to_scene_layer(&scene.layers.panel_text); label.add_to_scene_layer(&scene.layers.panel_text);
let text_color_path = theme::application::status_bar::text; let text_color_path = theme::application::status_bar::text;
let style = StyleWatch::new(&app.display.scene().style_sheet); let style = StyleWatch::new(&app.display.default_scene.style_sheet);
let text_color = style.get_color(text_color_path); let text_color = style.get_color(text_color_path);
label.frp.set_color_all.emit(text_color); label.frp.set_color_all.emit(text_color);
label.frp.set_default_color.emit(text_color); label.frp.set_default_color.emit(text_color);
@ -279,7 +279,7 @@ impl View {
let frp = Frp::new(); let frp = Frp::new();
let model = Model::new(app); let model = Model::new(app);
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
enso_frp::extend! { network enso_frp::extend! { network
event_added <- frp.add_event.map(f!((label) model.add_event(label))); event_added <- frp.add_event.map(f!((label) model.add_event(label)));

View File

@ -150,7 +150,7 @@ impl Model {
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
ensogl::shapes_order_dependencies! { ensogl::shapes_order_dependencies! {
app.display.scene() => { app.display.default_scene => {
shape -> close::shape; shape -> close::shape;
shape -> fullscreen::shape; shape -> fullscreen::shape;
} }
@ -233,7 +233,7 @@ impl View {
let model = Model::new(app); let model = Model::new(app);
let network = &frp.network; let network = &frp.network;
let style = StyleWatchFrp::new(&app.display.scene().style_sheet); let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let style_frp = LayoutParams::from_theme(&style); let style_frp = LayoutParams::from_theme(&style);
let layout_style = style_frp.flatten(network); let layout_style = style_frp.flatten(network);
let radius = style.get_number(theme::radius); let radius = style.get_number(theme::radius);

View File

@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
ensogl = { path = "../../../../lib/rust/ensogl" } ensogl = { path = "../../../../lib/rust/ensogl" }
enso-frp = { path = "../../../../lib/rust/frp" } enso-frp = { path = "../../../../lib/rust/frp" }
wasm-bindgen = { version = "0.2.58", features = [ wasm-bindgen = { version = "0.2.78", features = [
"nightly", "nightly",
"serde-serialize" "serde-serialize"
] } ] }

View File

@ -19,14 +19,12 @@ use ensogl::application::Application;
use ensogl::display; use ensogl::display;
use ensogl::display::DomSymbol; use ensogl::display::DomSymbol;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::NodeInserter; use ensogl::system::web::traits::*;
use ensogl::system::web::StyleSetter;
use std::rc::Rc; use std::rc::Rc;
use wasm_bindgen::closure::Closure; use web::Closure;
use wasm_bindgen::JsCast; use web::Element;
use web_sys::Element; use web::HtmlDivElement;
use web_sys::HtmlDivElement; use web::MouseEvent;
use web_sys::MouseEvent;
@ -83,26 +81,23 @@ impl Deref for ClickableElement {
} }
impl ClickableElement { impl ClickableElement {
pub fn new(element: Element, logger: &Logger) -> Self { pub fn new(element: Element) -> Self {
frp::new_network! { network frp::new_network! { network
click <- source_(); click <- source_();
} }
let closure: ClickClosure = Closure::wrap(Box::new(f_!(click.emit(())))); let closure: ClickClosure = Closure::new(f_!(click.emit(())));
let callback = closure.as_ref().unchecked_ref(); let handle = web::add_event_listener(&element, "click", closure);
if element.add_event_listener_with_callback("click", callback).is_err() { network.store(&handle);
error!(logger, "Could not add event listener for ClickableElement.");
}
network.store(&Rc::new(closure));
Self { element, network, click } Self { element, network, click }
} }
} }
// ============= // =============
// === Model === // === Model ===
// ============= // =============
// === CSS Styles === // === CSS Styles ===
static STYLESHEET: &str = include_str!("../style.css"); static STYLESHEET: &str = include_str!("../style.css");
@ -130,42 +125,37 @@ impl Model {
let side_menu = SideMenu::new(&logger); let side_menu = SideMenu::new(&logger);
let template_cards = TemplateCards::new(&logger); let template_cards = TemplateCards::new(&logger);
let dom = Self::create_dom(&logger, &side_menu, &template_cards); let dom = Self::create_dom(&side_menu, &template_cards);
display_object.add_child(&dom); display_object.add_child(&dom);
// Use `welcome_screen` layer to lock position when panning. // Use `welcome_screen` layer to lock position when panning.
app.display.scene().dom.layers.welcome_screen.manage(&dom); app.display.default_scene.dom.layers.welcome_screen.manage(&dom);
let style = web::create_element("style"); let style = web::document.create_element_or_panic("style");
style.set_inner_html(STYLESHEET); style.set_inner_html(STYLESHEET);
dom.append_or_warn(&style, &logger); dom.append_or_warn(&style);
Self { application, logger, dom, display_object, side_menu, template_cards } Self { application, logger, dom, display_object, side_menu, template_cards }
} }
fn create_dom( fn create_dom(side_menu: &SideMenu, template_cards: &TemplateCards) -> DomSymbol {
logger: &Logger, let root = web::document.create_div_or_panic();
side_menu: &SideMenu,
template_cards: &TemplateCards,
) -> DomSymbol {
let root = web::create_div();
root.set_class_name(css_class::TEMPLATES_VIEW_ROOT); root.set_class_name(css_class::TEMPLATES_VIEW_ROOT);
// We explicitly enable pointer events for Welcome Screen elements. Pointer events are // We explicitly enable pointer events for Welcome Screen elements. Pointer events are
// disabled for all DOM layers by default. See [`DomLayers`] documentation. // disabled for all DOM layers by default. See [`DomLayers`] documentation.
root.set_style_or_warn("pointer-events", "auto", logger); root.set_style_or_warn("pointer-events", "auto");
let container = Self::create_content_container(); let container = Self::create_content_container();
container.append_or_warn(&side_menu.model.root_dom, logger); container.append_or_warn(&side_menu.model.root_dom);
container.append_or_warn(&template_cards.model.root_dom, logger); container.append_or_warn(&template_cards.model.root_dom);
root.append_or_warn(&container, logger); root.append_or_warn(&container);
DomSymbol::new(&root) DomSymbol::new(&root)
} }
fn create_content_container() -> HtmlDivElement { fn create_content_container() -> HtmlDivElement {
let container = web::create_div(); let container = web::document.create_div_or_panic();
container.set_class_name(css_class::CONTAINER); container.set_class_name(css_class::CONTAINER);
container container
} }
} }
@ -220,7 +210,7 @@ impl View {
frp::extend! { network frp::extend! { network
// === Update DOM's size so CSS styles work correctly. === // === Update DOM's size so CSS styles work correctly. ===
let scene_size = app.display.scene().shape().clone_ref(); let scene_size = app.display.default_scene.shape().clone_ref();
eval scene_size ((size) model.dom.set_size(Vector2::from(*size))); eval scene_size ((size) model.dom.set_size(Vector2::from(*size)));
} }
frp::extend! { network frp::extend! { network

View File

@ -8,34 +8,33 @@ use crate::ClickableElement;
use enso_frp as frp; use enso_frp as frp;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::NodeInserter; use ensogl::system::web::traits::*;
use web_sys::Element;
// ================ // =============
// === Model === // === Model ===
// ================ // =============
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug)]
pub struct Model { pub struct Model {
logger: Logger, logger: Logger,
pub root_dom: Element, pub root_dom: web::Element,
new_project_button: ClickableElement, new_project_button: ClickableElement,
projects_list_dom: Element, projects_list_dom: web::Element,
projects: Rc<RefCell<Vec<ClickableElement>>>, projects: Rc<RefCell<Vec<ClickableElement>>>,
} }
impl Model { impl Model {
/// Constructor. /// Constructor.
pub fn new(logger: Logger) -> Self { pub fn new(logger: Logger) -> Self {
let root_dom = web::create_element("aside"); let root_dom = web::document.create_element_or_panic("aside");
root_dom.set_class_name(crate::css_class::SIDE_MENU); root_dom.set_class_name(crate::css_class::SIDE_MENU);
let header = Self::create_header("Your projects"); let header = Self::create_header("Your projects");
root_dom.append_or_warn(&header, &logger); root_dom.append_or_warn(&header);
let projects_list_dom = Self::create_projects_list(); let projects_list_dom = Self::create_projects_list();
root_dom.append_or_warn(&projects_list_dom, &logger); root_dom.append_or_warn(&projects_list_dom);
let new_project_button = Self::create_new_project_button(&logger, &projects_list_dom); let new_project_button = Self::create_new_project_button(&projects_list_dom);
let projects = default(); let projects = default();
Self { logger, root_dom, projects_list_dom, projects, new_project_button } Self { logger, root_dom, projects_list_dom, projects, new_project_button }
@ -56,38 +55,38 @@ impl Model {
} }
fn add_projects_list_entry(&self, name: &str, open_project: &frp::Any<String>) { fn add_projects_list_entry(&self, name: &str, open_project: &frp::Any<String>) {
let entry = Self::create_project_list_entry(name, &self.logger); let entry = Self::create_project_list_entry(name);
let network = &entry.network; let network = &entry.network;
frp::extend! { network frp::extend! { network
open_project <+ entry.click.constant(name.to_owned()); open_project <+ entry.click.constant(name.to_owned());
} }
let new_project_button = &self.new_project_button; let new_project_button = &self.new_project_button;
self.projects_list_dom.insert_before_or_warn(&entry, new_project_button, &self.logger); self.projects_list_dom.insert_before_or_warn(&entry, new_project_button);
self.projects.borrow_mut().push(entry); self.projects.borrow_mut().push(entry);
} }
fn create_new_project_button(logger: &Logger, projects_list: &Element) -> ClickableElement { fn create_new_project_button(projects_list: &web::Element) -> ClickableElement {
let element = web::create_element("li"); let element = web::document.create_element_or_panic("li");
element.set_id(crate::css_id::NEW_PROJECT); element.set_id(crate::css_id::NEW_PROJECT);
element.set_inner_html(r#"<img src="/assets/new-project.svg" />Create a new project"#); element.set_inner_html(r#"<img src="/assets/new-project.svg" />Create a new project"#);
projects_list.append_or_warn(&element, logger); projects_list.append_or_warn(&element);
ClickableElement::new(element, logger) ClickableElement::new(element)
} }
fn create_header(text: &str) -> Element { fn create_header(text: &str) -> web::Element {
let header = web::create_element("h2"); let header = web::document.create_element_or_panic("h2");
header.set_text_content(Some(text)); header.set_text_content(Some(text));
header header
} }
fn create_projects_list() -> Element { fn create_projects_list() -> web::Element {
web::create_element("ul") web::document.create_element_or_panic("ul")
} }
fn create_project_list_entry(project_name: &str, logger: &Logger) -> ClickableElement { fn create_project_list_entry(project_name: &str) -> ClickableElement {
let element = web::create_element("li"); let element = web::document.create_element_or_panic("li");
element.set_inner_html(&format!(r#"<img src="assets/project.svg"/> {}"#, project_name)); element.set_inner_html(&format!(r#"<img src="assets/project.svg"/> {}"#, project_name));
ClickableElement::new(element, logger) ClickableElement::new(element)
} }
} }

View File

@ -9,11 +9,10 @@ use crate::ClickableElement;
use enso_frp as frp; use enso_frp as frp;
use ensogl::system::web; use ensogl::system::web;
use ensogl::system::web::AttributeSetter; use ensogl::system::web::traits::*;
use ensogl::system::web::NodeInserter; use web::Element;
use wasm_bindgen::JsCast; use web::HtmlDivElement;
use web_sys::Element; use web::JsCast;
use web_sys::HtmlDivElement;
@ -88,16 +87,16 @@ pub struct Model {
impl Model { impl Model {
/// Constructor. /// Constructor.
pub fn new(logger: Logger, open_template: &frp::Any<String>) -> Self { pub fn new(logger: Logger, open_template: &frp::Any<String>) -> Self {
let root_dom = web::create_element("main"); let root_dom = web::document.create_element_or_panic("main");
root_dom.set_class_name(crate::css_class::CONTENT); root_dom.set_class_name(crate::css_class::CONTENT);
let templates = web::create_div(); let templates = web::document.create_div_or_panic();
let header = Self::create_header("Templates"); let header = Self::create_header("Templates");
templates.append_or_warn(&header, &logger); templates.append_or_warn(&header);
let (cards_dom, cards) = Self::create_cards(&logger); let (cards_dom, cards) = Self::create_cards();
templates.append_or_warn(&cards_dom, &logger); templates.append_or_warn(&cards_dom);
root_dom.append_or_warn(&templates, &logger); root_dom.append_or_warn(&templates);
let model = Self { logger, root_dom, cards: Rc::new(cards) }; let model = Self { logger, root_dom, cards: Rc::new(cards) };
model.setup_event_listeners(open_template); model.setup_event_listeners(open_template);
@ -116,58 +115,54 @@ impl Model {
} }
fn create_header(content: &str) -> Element { fn create_header(content: &str) -> Element {
let header = web::create_element("h2"); let header = web::document.create_element_or_panic("h2");
header.set_text_content(Some(content)); header.set_text_content(Some(content));
header header
} }
/// Create main content, a set of cards. /// Create main content, a set of cards.
fn create_cards(logger: &Logger) -> (HtmlDivElement, Vec<Card>) { fn create_cards() -> (HtmlDivElement, Vec<Card>) {
let mut cards = Vec::new(); let mut cards = Vec::new();
let dom = web::create_div(); let dom = web::document.create_div_or_panic();
dom.set_class_name(crate::css_class::CARDS); dom.set_class_name(crate::css_class::CARDS);
let row1 = Self::create_row(&[CARD_SPREADSHEETS, CARD_GEO], &mut cards, logger); let row1 = Self::create_row(&[CARD_SPREADSHEETS, CARD_GEO], &mut cards);
dom.append_or_warn(&row1, logger); dom.append_or_warn(&row1);
let row2 = Self::create_row(&[CARD_VISUALIZE], &mut cards, logger); let row2 = Self::create_row(&[CARD_VISUALIZE], &mut cards);
dom.append_or_warn(&row2, logger); dom.append_or_warn(&row2);
(dom, cards) (dom, cards)
} }
fn create_row( fn create_row(definitions: &[CardDefinition], cards: &mut Vec<Card>) -> HtmlDivElement {
definitions: &[CardDefinition], let row = web::document.create_div_or_panic();
cards: &mut Vec<Card>,
logger: &Logger,
) -> HtmlDivElement {
let row = web::create_div();
row.set_class_name(crate::css_class::ROW); row.set_class_name(crate::css_class::ROW);
for definition in definitions { for definition in definitions {
let card = Self::create_card(definition, logger); let card = Self::create_card(definition);
row.append_or_warn(&card.element, logger); row.append_or_warn(&card.element);
cards.push(card.clone()); cards.push(card.clone());
} }
row row
} }
/// Helper to create a single card DOM from provided definition. /// Helper to create a single card DOM from provided definition.
fn create_card(definition: &CardDefinition, logger: &Logger) -> Card { fn create_card(definition: &CardDefinition) -> Card {
let card = web::create_div(); let card = web::document.create_div_or_panic();
card.set_class_name(&format!("{} {}", crate::css_class::CARD, definition.class)); card.set_class_name(&format!("{} {}", crate::css_class::CARD, definition.class));
if let Some(src) = definition.background_image_url { if let Some(src) = definition.background_image_url {
let img = web::create_element("img"); let img = web::document.create_element_or_panic("img");
img.set_attribute_or_warn("src", src, logger); img.set_attribute_or_warn("src", src);
card.append_or_warn(&img, logger); card.append_or_warn(&img);
} }
let card_header = web::create_element("h3"); let card_header = web::document.create_element_or_panic("h3");
card_header.set_text_content(Some(definition.header)); card_header.set_text_content(Some(definition.header));
card.append_or_warn(&card_header, logger); card.append_or_warn(&card_header);
let text_content = web::create_element("p"); let text_content = web::document.create_element_or_panic("p");
text_content.set_text_content(Some(definition.content)); text_content.set_text_content(Some(definition.content));
card.append_or_warn(&text_content, logger); card.append_or_warn(&text_content);
let clickable_element = ClickableElement::new(card.unchecked_into(), logger); let clickable_element = ClickableElement::new(card.unchecked_into());
Card { clickable_element, template_name: definition.template } Card { clickable_element, template_name: definition.template }
} }
} }

View File

@ -188,7 +188,7 @@ function printScamWarning() {
'copy-paste something here, it is a scam and will give them access to your ' + 'copy-paste something here, it is a scam and will give them access to your ' +
'account and data.' 'account and data.'
let msg2 = let msg2 =
'See https://github.com/enso-org/enso/tree/develop/gui/docs/security/selfxss.md for more ' + 'See https://github.com/enso-org/enso/blob/develop/docs/security/selfxss.md for more ' +
'information.' 'information.'
console.log('%cStop!', headerCSS1) console.log('%cStop!', headerCSS1)
console.log('%cYou may be victim of a scam!', headerCSS2) console.log('%cYou may be victim of a scam!', headerCSS2)

View File

@ -1,7 +0,0 @@
A simple http server that takes POST requests for path "/" on localhost and
writes their body to a new file in the "log" subdirectory of the current working
directory
To start the server, make sure that all necessary packages are installed
(through Lerna) and call `npm run start`. To learn more about the options and
their default values, use the `--help` option.

View File

@ -1,31 +0,0 @@
{
"author": {
"name": "Enso Team",
"email": "contact@luna-lang.org"
},
"homepage": "https://github.com/luna/ide",
"repository": {
"type": "git",
"url": "git@github.com:luna/ide.git"
},
"bugs": {
"url": "https://github.com/luna/ide/issues"
},
"name": "enso-studio-logging-service",
"description": "A logging service that can receive crash reports from the Enso IDE.",
"main": "server.js",
"dependencies": {
"express": "^4.17.1",
"uuid": "^8.3.1",
"yargs": "^16.1.1"
},
"scripts": {
"start": "node server.js",
"test": "mocha"
},
"devDependencies": {
"axios": "^0.21.0",
"mocha": "^8.2.1",
"mock-fs": "^4.13.0"
}
}

View File

@ -1,119 +0,0 @@
// This module implements a simple http server that listens for crash reports as POST requests and
// writes them to disk. To start the server, call `startServer(port)` or execute this file through
// Node.js. More information can be foun in the README.md.
const express = require('express')
const fs = require('fs')
const uuid = require('uuid')
const yargs = require('yargs')
const defaultPort = require('../../config.js').defaultLogServerPort
module.exports = {
startServer,
}
function main(argv) {
startServer(parse_args(argv).port)
}
// =================
// === Constants ===
// =================
const httpStatusCodes = {
noContent: 204,
forbidden: 403,
badRequest: 400,
internalServerError: 500,
}
// ========================
// === Argument Parsing ===
// ========================
function parse_args(argv) {
return yargs(argv)
.option('port', {
alias: 'p',
description:
'The number of the port that this server will listen on. ' +
'If the the number is 0 then an arbitrary free port will be chosen.',
type: 'number',
default: defaultPort,
})
.help()
.alias('help', 'h').argv
}
// ==============
// === Server ===
// ==============
function startServer(port) {
const app = express()
app.use(express.text())
app.post('/', async (req, res) => {
if (
typeof req.headers.origin === 'undefined' ||
new URL(req.headers.origin).hostname !== 'localhost'
) {
res.sendStatus(httpStatusCodes.forbidden)
} else if (typeof req.body !== 'string') {
res.sendStatus(httpStatusCodes.badRequest)
} else {
try {
await writeLog(req.body)
console.log(`Saved log from origin ${req.headers.origin}`)
res.sendStatus(httpStatusCodes.noContent)
} catch (e) {
console.error('Could not write log file:\n' + e.message)
res.sendStatus(httpStatusCodes.internalServerError)
}
}
})
const server = app.listen(port, 'localhost')
server.on('listening', function () {
console.log(`Logging service listening at port ${server.address().port}`)
})
return server
}
// ==================
// === File Utils ===
// ==================
/**
* Writes message to a new file in the log sub directory.
* The file name is composed of the UTC time and date and a V4 UUID to guarantee uniqueness.
*/
async function writeLog(message) {
const dir = 'log'
const file = `${timestamp()}__${uuid.v4()}`
await fs.promises.mkdir(dir, { recursive: true })
await fs.promises.writeFile(`${dir}/${file}`, message)
}
/**
* Returns the current UTC date and time in the format "yyy-MM-dd_HH:mm:ss.".
*/
function timestamp() {
const d = new Date()
const year = d.getUTCFullYear().toString()
const month = d.getUTCMonth().toString().padStart(2, '0')
const day = d.getUTCDate().toString().padStart(2, '0')
const hour = d.getUTCHours().toString().padStart(2, '0')
const minute = d.getUTCMinutes().toString().padStart(2, '0')
const second = d.getUTCSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day}_${hour}:${minute}:${second}`
}
if (require.main === module) {
const command_line_args = process.argv.slice(2)
main(command_line_args)
}

View File

@ -1,80 +0,0 @@
const assert = require('assert')
const axios = require('axios').default
const fs = require('fs')
const mockFs = require('mock-fs')
const path = require('path')
const { startServer } = require('../server')
describe('Logging Server', function () {
let server
let serverUrl
const dummyMessage = '<Dummy message>'
const goodConfig = {
headers: {
'Content-Type': 'text/plain',
Origin: 'http://localhost/',
},
}
const wrongOriginConfig = {
headers: {
'Content-Type': 'text/plain',
Origin: 'http://attacker/',
},
}
const wrongContentTypeConfig = {
headers: {
'Content-Type': 'image/jpeg',
Origin: 'http://localhost/',
},
}
beforeEach(function (done) {
// The server relies on some package that imports from `raw-body` at runtime.
// Therefore, we need to include this directory in our mocked fs.
const rawBodyDir = path.dirname(require.resolve('raw-body'))
// We mock the file system so the server does not actually write logs to disk.
mockFs({
[rawBodyDir]: mockFs.load(rawBodyDir),
})
const port = 0 // Choose an arbitrary available port
server = startServer(port)
server.on('listening', function () {
serverUrl = `http://localhost:${server.address().port}/`
done()
})
})
afterEach(function () {
server.close()
mockFs.restore()
})
it('should write the body of valid requests to a file', async function () {
await axios.post(serverUrl, dummyMessage, goodConfig)
const log_files = fs.readdirSync('log/')
assert.strictEqual(log_files.length, 1)
assert.strictEqual(fs.readFileSync(`log/${log_files[0]}`).toString(), dummyMessage)
})
it('should reject requests from origins other than localhost', async function () {
let req = axios.post(serverUrl, '', wrongOriginConfig)
await assert.rejects(req, 'Error: Request failed with status code 403')
assert(!fs.existsSync('log/'))
})
it('should keep running after requests', async function () {
await Promise.allSettled([
axios.post(serverUrl, '', goodConfig),
axios.post(serverUrl, '', wrongOriginConfig),
axios.post(serverUrl, '', wrongContentTypeConfig),
])
await axios.post(serverUrl, dummyMessage, goodConfig)
const log_files = fs.readdirSync('log/')
assert(log_files.some(file => fs.readFileSync(`log/${file}`).toString() === dummyMessage))
})
})

View File

@ -21,9 +21,8 @@ use enso_frp::future::EventOutputExt;
use enso_gui::executor::web::EventLoopExecutor; use enso_gui::executor::web::EventLoopExecutor;
use enso_gui::initializer::setup_global_executor; use enso_gui::initializer::setup_global_executor;
use enso_gui::Ide; use enso_gui::Ide;
use enso_web::traits::*;
use enso_web::HtmlDivElement; use enso_web::HtmlDivElement;
use enso_web::NodeInserter;
use enso_web::StyleSetter;
use ensogl::application::Application; use ensogl::application::Application;
/// Reexports of commonly-used structures, methods and traits. /// Reexports of commonly-used structures, methods and traits.
@ -57,12 +56,11 @@ impl IntegrationTest {
/// Initializes the executor and `Ide` structure and returns new Fixture. /// Initializes the executor and `Ide` structure and returns new Fixture.
pub async fn setup() -> Self { pub async fn setup() -> Self {
enso_web::forward_panic_hook_to_error();
let executor = setup_global_executor(); let executor = setup_global_executor();
let root_div = enso_web::create_div(); let root_div = enso_web::document.create_div_or_panic();
root_div.set_id("root"); root_div.set_id("root");
root_div.set_style_or_panic("display", "none"); root_div.set_style_or_warn("display", "none");
enso_web::body().append_or_panic(&root_div); enso_web::document.body_or_panic().append_or_warn(&root_div);
let initializer = enso_gui::ide::Initializer::new(default()); let initializer = enso_gui::ide::Initializer::new(default());
let ide = initializer.start().await.expect("Failed to initialize the application."); let ide = initializer.start().await.expect("Failed to initialize the application.");
@ -72,7 +70,7 @@ impl IntegrationTest {
fn set_screen_size(app: &Application) { fn set_screen_size(app: &Application) {
let (screen_width, screen_height) = Self::SCREEN_SIZE; let (screen_width, screen_height) = Self::SCREEN_SIZE;
app.display.scene().layers.iter_sublayers_and_masks_nested(|layer| { app.display.default_scene.layers.iter_sublayers_and_masks_nested(|layer| {
layer.camera().set_screen(screen_width, screen_height) layer.camera().set_screen(screen_width, screen_height)
}); });
} }

View File

@ -75,7 +75,7 @@ async fn zooming() {
let test = IntegrationTestOnNewProject::setup().await; let test = IntegrationTestOnNewProject::setup().await;
let project = test.project_view(); let project = test.project_view();
let graph_editor = test.graph_editor(); let graph_editor = test.graph_editor();
let camera = test.ide.ensogl_app.display.scene().layers.main.camera(); let camera = test.ide.ensogl_app.display.default_scene.layers.main.camera();
let navigator = &graph_editor.model.navigator; let navigator = &graph_editor.model.navigator;
let zoom_on_center = |amount: f32| ZoomEvent { focus: Vector2(0.0, 0.0), amount }; let zoom_on_center = |amount: f32| ZoomEvent { focus: Vector2(0.0, 0.0), amount };
@ -136,7 +136,7 @@ async fn adding_node_with_add_node_button() {
assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 2); assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 2);
// If there is a free space, the new node is created in the center of screen. // If there is a free space, the new node is created in the center of screen.
let camera = test.ide.ensogl_app.display.scene().layers.main.camera(); let camera = test.ide.ensogl_app.display.default_scene.layers.main.camera();
camera.mod_position_xy(|pos| pos + Vector2(1000.0, 1000.0)); camera.mod_position_xy(|pos| pos + Vector2(1000.0, 1000.0));
let wait_for_update = Duration::from_millis(500); let wait_for_update = Duration::from_millis(500);
sleep(wait_for_update).await; sleep(wait_for_update).await;
@ -145,8 +145,8 @@ async fn adding_node_with_add_node_button() {
assert!(node_source.is_none()); assert!(node_source.is_none());
assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 3); assert_eq!(graph_editor.model.nodes.all.len(), INITIAL_NODE_COUNT + 3);
let node_position = graph_editor.model.get_node_position(node_id).expect("Node was not added"); let node_position = graph_editor.model.get_node_position(node_id).expect("Node was not added");
let center_of_screen = let scene = &test.ide.ensogl_app.display.default_scene;
test.ide.ensogl_app.display.scene().screen_to_scene_coordinates(Vector3(0.0, 0.0, 0.0)); let center_of_screen = scene.screen_to_scene_coordinates(Vector3(0.0, 0.0, 0.0));
assert_abs_diff_eq!(node_position.x, center_of_screen.x, epsilon = 10.0); assert_abs_diff_eq!(node_position.x, center_of_screen.x, epsilon = 10.0);
assert_abs_diff_eq!(node_position.y, center_of_screen.y, epsilon = 10.0); assert_abs_diff_eq!(node_position.y, center_of_screen.y, epsilon = 10.0);
} }

View File

@ -1,47 +1,66 @@
#![feature(trait_alias)] //! Definitions of a callback registry utility allowing attaching and running attached functions.
//! Definitions of callback handling utilities. // === Linter configuration ===
#![warn(missing_copy_implementations)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts)]
#![warn(trivial_numeric_casts)]
#![warn(unsafe_code)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
// === Features ===
#![feature(trait_alias)]
#![feature(unboxed_closures)]
#![feature(fn_traits)]
#![feature(unsize)]
use enso_prelude::*; use enso_prelude::*;
use std::any::TypeId; use std::any::TypeId;
use std::marker::Unsize;
// ================ // ==============================
// === Callback === // === Popular Callback Types ===
// ================ // ==============================
/// Immutable callback type. pub use callback_types::*;
pub trait CallbackFn = Fn() + 'static;
/// Immutable callback object. /// Popular callback types. These are aliases for static [`Fn`] and [`FnMut`] with different amount
pub type Callback = Box<dyn CallbackFn>; /// of arguments. The names directly correspond to the [`::registry`] namespace. For example,
/// the [`::registry::CopyMut3`] is a callback registry for [`CopyMut3`] callbacks.
#[allow(missing_docs)]
mod callback_types {
pub trait NoArgs = 'static + Fn();
pub trait MutNoArgs = 'static + FnMut();
/// Callback object smart constructor. pub trait Copy1<T1> = 'static + Fn(T1);
#[allow(non_snake_case)] pub trait Copy2<T1, T2> = 'static + Fn(T1, T2);
pub fn Callback<F: CallbackFn>(f: F) -> Callback { pub trait Copy3<T1, T2, T3> = 'static + Fn(T1, T2, T3);
Box::new(f) pub trait Copy4<T1, T2, T3, T4> = 'static + Fn(T1, T2, T3, T4);
pub trait Copy5<T1, T2, T3, T4, T5> = 'static + Fn(T1, T2, T3, T4, T5);
pub trait Ref1<T1> = 'static + Fn(&T1);
pub trait Ref2<T1, T2> = 'static + Fn(&T1, &T2);
pub trait Ref3<T1, T2, T3> = 'static + Fn(&T1, &T2, &T3);
pub trait Ref4<T1, T2, T3, T4> = 'static + Fn(&T1, &T2, &T3, &T4);
pub trait Ref5<T1, T2, T3, T4, T5> = 'static + Fn(&T1, &T2, &T3, &T4, &T5);
pub trait CopyMut1<T1> = 'static + FnMut(T1);
pub trait CopyMut2<T1, T2> = 'static + FnMut(T1, T2);
pub trait CopyMut3<T1, T2, T3> = 'static + FnMut(T1, T2, T3);
pub trait CopyMut4<T1, T2, T3, T4> = 'static + FnMut(T1, T2, T3, T4);
pub trait CopyMut5<T1, T2, T3, T4, T5> = 'static + FnMut(T1, T2, T3, T4, T5);
pub trait RefMut1<T1> = 'static + FnMut(&T1);
pub trait RefMut2<T1, T2> = 'static + FnMut(&T1, &T2);
pub trait RefMut3<T1, T2, T3> = 'static + FnMut(&T1, &T2, &T3);
pub trait RefMut4<T1, T2, T3, T4> = 'static + FnMut(&T1, &T2, &T3, &T4);
pub trait RefMut5<T1, T2, T3, T4, T5> = 'static + FnMut(&T1, &T2, &T3, &T4, &T5);
} }
/// Mutable callback type.
pub trait CallbackMutFn = FnMut() + 'static;
/// Mutable callback object.
pub type CallbackMut = Box<dyn CallbackMutFn>;
/// Mutable callback type with one parameter.
pub trait CallbackMut1Fn<T> = FnMut(&T) + 'static;
/// Mutable callback object with one parameter.
pub type CallbackMut1<T> = Box<dyn CallbackMut1Fn<T>>;
/// Mutable callback type with one parameter.
pub trait CopyCallbackMut1Fn<T> = FnMut(T) + 'static;
/// Mutable callback object with one parameter.
pub type CopyCallbackMut1<T> = Box<dyn CopyCallbackMut1Fn<T>>;
// ============== // ==============
@ -49,27 +68,21 @@ pub type CopyCallbackMut1<T> = Box<dyn CopyCallbackMut1Fn<T>>;
// ============== // ==============
/// Handle to a callback. When the handle is dropped, the callback is removed. /// Handle to a callback. When the handle is dropped, the callback is removed.
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug, Default)]
pub struct Handle { pub struct Handle {
rc: Rc<Cell<bool>>, is_invalidated: Rc<Cell<bool>>,
} }
impl Handle { impl Handle {
/// Constructor.
pub fn new() -> Self {
let rc = Rc::new(Cell::new(true));
Self { rc }
}
/// Create guard for this handle. /// Create guard for this handle.
pub fn guard(&self) -> Guard { pub fn guard(&self) -> Guard {
Guard { weak: Rc::downgrade(&self.rc) } Guard { weak: Rc::downgrade(&self.is_invalidated) }
} }
/// Invalidates all handles. Even if there exist some active handles, the callback will not be /// Invalidates all handles. Even if there exist some active handles, the callback will not be
/// run anymore after performing this operation. /// run anymore after performing this operation.
pub fn invalidate_all_handles(&self) { pub fn invalidate_all_handles(&self) {
self.rc.set(false) self.is_invalidated.set(true)
} }
/// Forget the handle. Warning! You would not be able to stop the callback after performing this /// Forget the handle. Warning! You would not be able to stop the callback after performing this
@ -79,12 +92,6 @@ impl Handle {
} }
} }
impl Default for Handle {
fn default() -> Self {
Self::new()
}
}
// ============= // =============
@ -100,10 +107,75 @@ pub struct Guard {
impl Guard { impl Guard {
/// Checks if the handle is still valid. /// Checks if the handle is still valid.
pub fn exists(&self) -> bool { pub fn exists(&self) -> bool {
match self.weak.upgrade() { self.weak.upgrade().map_or(false, |t| !t.get())
None => false, }
Some(t) => t.get(), }
}
// ==================
// === RegistryFn ===
// ==================
/// An abstraction for a [`Fn`] functions kept in the [`Registry`]. It is used to unify the handling
/// of [`Fn`] and [`FnMut`] functions. They are kept either in this structure or in the
/// [`RegistryFnMut`] one, both exposing the same API.
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Debug(bound = ""))]
pub struct RegistryFn<F: ?Sized> {
#[derivative(Debug = "ignore")]
function: Rc<F>,
}
/// An abstraction for a [`FnMut`] functions kept in the [`Registry`]. See the documentation of
/// [`RegistryFn`] to learn more.
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Debug(bound = ""))]
pub struct RegistryFnMut<F: ?Sized> {
#[derivative(Debug = "ignore")]
function: Rc<RefCell<F>>,
}
/// Constructor abstraction for [`RegistryFn`] and [`RegistryFnMut`].
#[allow(missing_docs)]
pub trait RegistryFnNew {
type InternalF: ?Sized;
fn new<C: Unsize<Self::InternalF> + 'static>(f: C) -> Self;
}
/// Call abstraction for [`RegistryFn`] and [`RegistryFnMut`].
#[allow(missing_docs)]
pub trait RegistryFnCall<Args> {
fn call(&self, args: Args);
}
impl<F: ?Sized> RegistryFnNew for RegistryFn<F> {
type InternalF = F;
fn new<C: Unsize<F> + 'static>(f: C) -> Self {
let function = Rc::new(f);
Self { function }
}
}
impl<F: ?Sized> RegistryFnNew for RegistryFnMut<F> {
type InternalF = F;
fn new<C: Unsize<F> + 'static>(f: C) -> Self {
let function = Rc::new(RefCell::new(f));
Self { function }
}
}
impl<Args, F: ?Sized + Fn<Args>> RegistryFnCall<Args> for RegistryFn<F> {
fn call(&self, args: Args) {
(&*self.function).call(args);
}
}
impl<Args, F: ?Sized + FnMut<Args>> RegistryFnCall<Args> for RegistryFnMut<F> {
fn call(&self, args: Args) {
(&mut *self.function.borrow_mut()).call_mut(args);
} }
} }
@ -113,117 +185,32 @@ impl Guard {
// === Registry === // === Registry ===
// ================ // ================
/// Registry gathering callbacks. Each registered callback is assigned with a handle. Callback and /// The main callback registry structure. The [`F`] parameter is either instantiated to
/// handle lifetimes are strictly connected. As soon a handle is dropped, the callback is removed /// [`RegistryFn<dyn Fn<Args>>`] or to [`RegistryFnMut<dyn FnMut<Args>>`]. See the generated aliases
/// as well. /// for common types below, in the [`registry`] module.
#[derive(Default, Derivative)]
#[derivative(Debug)]
pub struct Registry {
#[derivative(Debug = "ignore")]
callback_list: Vec<(Guard, CallbackMut)>,
}
impl Registry {
/// Adds new callback and returns a new handle for it.
pub fn add<F: CallbackMutFn>(&mut self, callback: F) -> Handle {
let callback = Box::new(callback);
let handle = Handle::new();
let guard = handle.guard();
self.callback_list.push((guard, callback));
handle
}
///Checks whether there are any callbacks registered.
pub fn is_empty(&self) -> bool {
self.callback_list.is_empty()
}
/// Fires all registered callbacks and removes the ones which got dropped.
pub fn run_all(&mut self) {
self.clear_unused_callbacks();
self.callback_list.iter_mut().for_each(move |(_, callback)| callback());
}
/// Checks all registered callbacks and removes the ones which got dropped.
fn clear_unused_callbacks(&mut self) {
self.callback_list.retain(|(guard, _)| guard.exists());
}
}
// =========================
// === SharedRegistryMut ===
// =========================
/// Registry gathering callbacks implemented with internal mutability pattern. Each registered
/// callback is assigned with a handle. Callback and handle lifetimes are strictly connected. As
/// soon a handle is dropped, the callback is removed as well.
#[derive(Clone, CloneRef, Default, Derivative)]
#[derivative(Debug)]
#[allow(clippy::type_complexity)]
pub struct SharedRegistryMut {
#[derivative(Debug = "ignore")]
callback_list: Rc<RefCell<Vec<(Guard, Rc<RefCell<dyn CallbackMutFn>>)>>>,
}
impl SharedRegistryMut {
/// Adds new callback and returns a new handle for it.
pub fn add<F: CallbackMutFn>(&self, callback: F) -> Handle {
let callback = Rc::new(RefCell::new(callback));
let handle = Handle::new();
let guard = handle.guard();
self.callback_list.borrow_mut().push((guard, callback));
handle
}
///Checks whether there are any callbacks registered.
pub fn is_empty(&self) -> bool {
self.callback_list.borrow().is_empty()
}
/// Fires all registered callbacks and removes the ones which got dropped. The implementation is
/// safe - you are allowed to change the registry while a callback is running.
pub fn run_all(&self) {
self.clear_unused_callbacks();
let callbacks = self.callback_list.borrow().clone();
callbacks.iter().for_each(|(_, callback)| (&mut *callback.borrow_mut())());
}
/// Checks all registered callbacks and removes the ones which got dropped.
fn clear_unused_callbacks(&self) {
self.callback_list.borrow_mut().retain(|(guard, _)| guard.exists());
}
}
// ==========================
// === SharedRegistryMut1 ===
// ==========================
/// Registry gathering callbacks implemented with internal mutability pattern. Each registered
/// callback is assigned with a handle. Callback and handle lifetimes are strictly connected. As
/// soon a handle is dropped, the callback is removed as well.
#[derive(CloneRef, Derivative)] #[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))] #[derivative(Clone(bound = ""))]
#[derivative(Debug(bound = ""))] #[derivative(Debug(bound = ""))]
#[derivative(Default(bound = ""))] #[derivative(Default(bound = ""))]
#[allow(clippy::type_complexity)] #[allow(missing_docs)]
pub struct SharedRegistryMut1<T> { pub struct Registry<F> {
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
callback_list: Rc<RefCell<Vec<(Guard, Rc<RefCell<dyn CallbackMut1Fn<T>>>)>>>, callback_list: Rc<RefCell<Vec<(Guard, F)>>>,
} }
impl<T> SharedRegistryMut1<T> { impl<F> Registry<F> {
/// Constructor. /// Constructor.
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
/// Adds new callback and returns a new handle for it. /// Add a new callback. Returns a new [`Handle`], which dropped, will unregister the callback.
pub fn add<F: CallbackMut1Fn<T>>(&self, callback: F) -> Handle { pub fn add<C>(&self, callback: C) -> Handle
let callback = Rc::new(RefCell::new(callback)); where
let handle = Handle::new(); F: RegistryFnNew,
C: Unsize<<F as RegistryFnNew>::InternalF> + 'static, {
let callback = F::new(callback);
let handle = Handle::default();
let guard = handle.guard(); let guard = handle.guard();
self.callback_list.borrow_mut().push((guard, callback)); self.callback_list.borrow_mut().push((guard, callback));
handle handle
@ -234,103 +221,164 @@ impl<T> SharedRegistryMut1<T> {
self.callback_list.borrow().is_empty() self.callback_list.borrow().is_empty()
} }
/// Fires all registered callbacks and removes the ones which got dropped. The implementation is
/// safe - you are allowed to change the registry while a callback is running.
pub fn run_all(&self, t: &T) {
self.clear_unused_callbacks();
let callbacks = self.callback_list.borrow().clone();
callbacks.iter().for_each(|(_, callback)| (&mut *callback.borrow_mut())(t));
}
/// Checks all registered callbacks and removes the ones which got dropped. /// Checks all registered callbacks and removes the ones which got dropped.
fn clear_unused_callbacks(&self) { fn clear_unused_callbacks(&self) {
self.callback_list.borrow_mut().retain(|(guard, _)| guard.exists()); self.callback_list.borrow_mut().retain(|(guard, _)| guard.exists());
} }
}
/// Fires all registered callbacks and removes the ones which got dropped. The implementation
/// is safe - you are allowed to change the registry while a callback is running.
// ================= pub fn run_all_with_args<Args: Copy>(&self, args: Args)
// === Registry1 === where F: Clone + RegistryFnCall<Args> {
// =================
/// Registry gathering callbacks. Each registered callback is assigned with a handle. Callback and
/// handle lifetimes are strictly connected. As soon a handle is dropped, the callback is removed
/// as well.
#[derive(Derivative)]
#[derivative(Debug, Default(bound = ""))]
pub struct Registry1<T> {
#[derivative(Debug = "ignore")]
callback_list: Vec<(Guard, CallbackMut1<T>)>,
}
impl<T> Registry1<T> {
/// Adds new callback and returns a new handle for it.
pub fn add<F: CallbackMut1Fn<T>>(&mut self, callback: F) -> Handle {
let callback = Box::new(callback);
let handle = Handle::new();
let guard = handle.guard();
self.callback_list.push((guard, callback));
handle
}
///Checks whether there are any callbacks registered.
pub fn is_empty(&self) -> bool {
self.callback_list.is_empty()
}
/// Fires all registered callbacks and removes the ones which got dropped.
pub fn run_all(&mut self, t: &T) {
self.clear_unused_callbacks(); self.clear_unused_callbacks();
self.callback_list.iter_mut().for_each(move |(_, callback)| callback(t)); // The clone is performed in order for the callbacks to be able to register new ones.
let callbacks = self.callback_list.borrow().clone();
callbacks.iter().for_each(move |(_, callback)| {
callback.call(args);
});
} }
}
/// Checks all registered callbacks and removes the ones which got dropped. /// Aliases for common [`Registry`] instantiations. The names directly correspond to the
fn clear_unused_callbacks(&mut self) { /// [`::callback_types`] namespace. For example, the [`::registry::CopyMut3`] is a callback registry
self.callback_list.retain(|(guard, _)| guard.exists()); /// for [`CopyMut3`] callbacks.
} ///
/// The used naming convention is `("Copy" | "Ref") ("Mut" | "") ("NoArgs" | <arg number>)`:
/// - The "Copy" prefix means that arguments are passed as copies. You should use this registry type
/// when working with callbacks consuming primitive types, such as [`usize`].
/// - The "Ref" prefix means that the arguments are passed as references. You should use this
/// registry type when working with callbacks consuming complex types, not implementing [`Copy`].
/// - The registry types which contain the "Mut" part accept `FnMut<Args>` functions. The rest
/// accepts the `Fn<Args>` ones.
/// - The arg number is a number from 1 to 5 indicating the number of arguments callbacks accept.
///
/// For example:
/// - The [`CopyMut2`] registry accepts callbacks in a form of [`FnMut(T1,T2)`], where both [`T1`]
/// and [`T2`] will be passed as copies.
/// - The [`Ref1`] registry accepts callbacks in a form of [`Fn(&T1)`].
/// - The [`NoArgs`] registry is a registry for [`Fn()`] functions.
/// - The [`MutNoArgs`] registry is a registry for [`FnMut()`] functions.
///
/// It is possible to define a registry which uses callbacks whose arguments are a mix of references
/// and copy-able values. However, such types need to be defined manually and are not provided by
/// the alias set below.
#[allow(missing_docs)]
pub mod registry {
use super::*;
pub type NoArgs = Registry<RegistryFn<dyn Fn()>>;
pub type MutNoArgs = Registry<RegistryFnMut<dyn FnMut()>>;
pub type Copy1<T1> = Registry<RegistryFn<dyn Fn(T1)>>;
pub type Copy2<T1, T2> = Registry<RegistryFn<dyn Fn(T1, T2)>>;
pub type Copy3<T1, T2, T3> = Registry<RegistryFn<dyn Fn(T1, T2, T3)>>;
pub type Copy4<T1, T2, T3, T4> = Registry<RegistryFn<dyn Fn(T1, T2, T3, T4)>>;
pub type Copy5<T1, T2, T3, T4, T5> = Registry<RegistryFn<dyn Fn(T1, T2, T3, T4, T5)>>;
pub type Ref1<T1> = Registry<RegistryFn<dyn Fn(&T1)>>;
pub type Ref2<T1, T2> = Registry<RegistryFn<dyn Fn(&T1, &T2)>>;
pub type Ref3<T1, T2, T3> = Registry<RegistryFn<dyn Fn(&T1, &T2, &T3)>>;
pub type Ref4<T1, T2, T3, T4> = Registry<RegistryFn<dyn Fn(&T1, &T2, &T3, &T4)>>;
pub type Ref5<T1, T2, T3, T4, T5> = Registry<RegistryFn<dyn Fn(&T1, &T2, &T3, &T4, &T5)>>;
pub type CopyMut1<T1> = Registry<RegistryFnMut<dyn FnMut(T1)>>;
pub type CopyMut2<T1, T2> = Registry<RegistryFnMut<dyn FnMut(T1, T2)>>;
pub type CopyMut3<T1, T2, T3> = Registry<RegistryFnMut<dyn FnMut(T1, T2, T3)>>;
pub type CopyMut4<T1, T2, T3, T4> = Registry<RegistryFnMut<dyn FnMut(T1, T2, T3, T4)>>;
pub type CopyMut5<T1, T2, T3, T4, T5> = Registry<RegistryFnMut<dyn FnMut(T1, T2, T3, T4, T5)>>;
pub type RefMut1<T1> = Registry<RegistryFnMut<dyn FnMut(&T1)>>;
pub type RefMut2<T1, T2> = Registry<RegistryFnMut<dyn FnMut(&T1, &T2)>>;
pub type RefMut3<T1, T2, T3> = Registry<RegistryFnMut<dyn FnMut(&T1, &T2, &T3)>>;
pub type RefMut4<T1, T2, T3, T4> = Registry<RegistryFnMut<dyn FnMut(&T1, &T2, &T3, &T4)>>;
pub type RefMut5<T1, T2, T3, T4, T5> =
Registry<RegistryFnMut<dyn FnMut(&T1, &T2, &T3, &T4, &T5)>>;
} }
// ==================== // ======================
// === CopyRegistry === // === RegistryRunner ===
// ==================== // ======================
/// Specialized version of `Registry` for arguments implementing `Copy`. Passing copy-able elements /// Generator of traits allowing the usage of a [`run_all`] function. It is an alias for the
/// as values is more performant than by reference. /// [`Registry::run_all_with_args`] where arguments are passed in a convenient way, instead than in
#[derive(Derivative)] /// a tuple.
#[derivative(Debug(bound = ""), Default(bound = ""))] macro_rules! gen_runner_traits {
pub struct CopyRegistry1<T> { ($name:ident, $ref_name:ident, ($($arg:ident),*)) => {
#[derivative(Debug = "ignore")] #[allow(non_snake_case)]
callback_list: Vec<(Guard, CopyCallbackMut1<T>)>, pub trait $name {
$( type $arg; )*
fn run_all(&self, $($arg : Self::$arg),*);
}
#[allow(non_snake_case)]
pub trait $ref_name {
$( type $arg; )*
fn run_all(&self, $($arg : &Self::$arg),*);
}
};
} }
impl<T: Copy> CopyRegistry1<T> { macro_rules! gen_runner {
/// Adds new callback and returns a new handle for it. ($name:ident, $ref_name:ident, $data:ident, $data_ref:ident, <$($arg:ident),*>) => {
pub fn add<F: CopyCallbackMut1Fn<T>>(&mut self, callback: F) -> Handle { #[allow(non_snake_case)]
let callback = Box::new(callback); impl<$($arg: Copy),*> $name for registry::$data<$($arg),*> {
let handle = Handle::new(); $(type $arg = $arg;)*
let guard = handle.guard(); fn run_all(&self, $($arg : Self::$arg),*) {
self.callback_list.push((guard, callback)); self.run_all_with_args(($($arg),*,))
handle }
}
#[allow(non_snake_case)]
impl<$($arg),*> $ref_name for registry::$data_ref<$($arg),*> {
$(type $arg = $arg;)*
fn run_all(&self, $($arg : &Self::$arg),*) {
self.run_all_with_args(($($arg),*,))
}
}
};
}
// ==============
// === Traits ===
// ==============
/// All trait types that should be imported to the scope when using callback registry.
#[allow(missing_docs)]
#[allow(non_camel_case_types)]
pub mod traits {
use super::*;
pub trait RegistryRunner0 {
fn run_all(&self);
} }
///Checks whether there are any callbacks registered. impl RegistryRunner0 for registry::MutNoArgs {
pub fn is_empty(&self) -> bool { fn run_all(&self) {
self.callback_list.is_empty() self.run_all_with_args(())
}
} }
/// Fires all registered callbacks and removes the ones which got dropped. impl RegistryRunner0 for registry::NoArgs {
pub fn run_all(&mut self, t: T) { fn run_all(&self) {
self.clear_unused_callbacks(); self.run_all_with_args(())
self.callback_list.iter_mut().for_each(move |(_, callback)| callback(t)); }
} }
/// Checks all registered callbacks and removes the ones which got dropped. gen_runner_traits!(RegistryRunner1, RegistryRunnerRef1, (T1));
fn clear_unused_callbacks(&mut self) { gen_runner_traits!(RegistryRunner2, RegistryRunnerRef2, (T1, T2));
self.callback_list.retain(|(guard, _)| guard.exists()); gen_runner_traits!(RegistryRunner3, RegistryRunnerRef3, (T1, T2, T3));
} gen_runner_traits!(RegistryRunner4, RegistryRunnerRef4, (T1, T2, T3, T4));
gen_runner_traits!(RegistryRunner5, RegistryRunnerRef5, (T1, T2, T3, T4, T5));
gen_runner!(RegistryRunner1, RegistryRunnerRef1, CopyMut1, RefMut1, <T1>);
gen_runner!(RegistryRunner2, RegistryRunnerRef2, CopyMut2, RefMut2, <T1,T2>);
gen_runner!(RegistryRunner3, RegistryRunnerRef3, CopyMut3, RefMut3, <T1,T2,T3>);
gen_runner!(RegistryRunner4, RegistryRunnerRef4, CopyMut4, RefMut4, <T1,T2,T3,T4>);
gen_runner!(RegistryRunner5, RegistryRunnerRef5, CopyMut5, RefMut5, <T1,T2,T3,T4,T5>);
} }
@ -360,17 +408,18 @@ impl DynEvent {
#[derivative(Debug)] #[derivative(Debug)]
pub struct DynEventDispatcher { pub struct DynEventDispatcher {
#[derivative(Debug = "ignore")] #[derivative(Debug = "ignore")]
listener_map: HashMap<TypeId, Vec<(Guard, CallbackMut1<DynEvent>)>>, #[allow(clippy::type_complexity)]
listener_map: HashMap<TypeId, Vec<(Guard, Box<dyn RefMut1<DynEvent>>)>>,
} }
impl DynEventDispatcher { impl DynEventDispatcher {
/// Registers a new listener for a given type. /// Registers a new listener for a given type.
pub fn add_listener<F: CallbackMut1Fn<T>, T: 'static>(&mut self, mut f: F) -> Handle { pub fn add_listener<F: RefMut1<T>, T: 'static>(&mut self, mut f: F) -> Handle {
let callback = Box::new(move |event: &DynEvent| { let callback = Box::new(move |event: &DynEvent| {
event.any.downcast_ref::<T>().iter().for_each(|t| f(t)) event.any.downcast_ref::<T>().iter().for_each(|t| f(t))
}); });
let type_id = (&PhantomData::<T>).type_id(); let type_id = (&PhantomData::<T>).type_id();
let handle = Handle::new(); let handle = Handle::default();
let guard = handle.guard(); let guard = handle.guard();
let listeners = self.listener_map.entry(type_id).or_insert_with(default); let listeners = self.listener_map.entry(type_id).or_insert_with(default);
listeners.push((guard, callback)); listeners.push((guard, callback));

View File

@ -274,7 +274,7 @@ impl<Shape: ButtonShape> View<Shape> {
let frp = Frp::new(); let frp = Frp::new();
let model = Model::<Shape>::new(app); let model = Model::<Shape>::new(app);
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let style = StyleWatchFrp::new(&scene.style_sheet); let style = StyleWatchFrp::new(&scene.style_sheet);
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;

View File

@ -220,7 +220,7 @@ impl DropDownMenu {
let frp = &self.frp; let frp = &self.frp;
let model = &self.model; let model = &self.model;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;
frp::extend! { network frp::extend! { network
@ -368,7 +368,7 @@ impl DropDownMenu {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for
// shape system (#795) // shape system (#795)
let styles = StyleWatch::new(&app.display.scene().style_sheet); let styles = StyleWatch::new(&app.display.default_scene.style_sheet);
let text_color = styles.get_color(theme::widget::list_view::text); let text_color = styles.get_color(theme::widget::list_view::text);
model.label.set_default_color(text_color); model.label.set_default_color(text_color);

View File

@ -10,7 +10,7 @@ enso-frp = { path = "../../../frp" }
enso-logger = { path = "../../../logger"} enso-logger = { path = "../../../logger"}
enso-prelude = { path = "../../../prelude"} enso-prelude = { path = "../../../prelude"}
js-sys = { version = "0.3.28" } js-sys = { version = "0.3.28" }
wasm-bindgen = { version = "0.2.58", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
wasm-bindgen-futures = { version = "0.4.8" } wasm-bindgen-futures = { version = "0.4.8" }
[dependencies.web-sys] [dependencies.web-sys]

View File

@ -21,12 +21,16 @@ pub mod prelude {
use crate::prelude::*; use crate::prelude::*;
use enso_frp as frp; use enso_frp as frp;
use enso_web as web;
use enso_web::stream::BlobExt; use enso_web::stream::BlobExt;
use enso_web::stream::ReadableStreamDefaultReader; use enso_web::stream::ReadableStreamDefaultReader;
use enso_web::Error; use enso_web::Closure;
#[cfg(target_arch = "wasm32")]
use enso_web::JsCast;
#[cfg(target_arch = "wasm32")]
use js_sys::Uint8Array; use js_sys::Uint8Array;
use wasm_bindgen::prelude::*; #[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
@ -54,7 +58,7 @@ pub struct File {
impl File { impl File {
/// Constructor from the [`web_sys::File`]. /// Constructor from the [`web_sys::File`].
pub fn from_js_file(file: &web_sys::File) -> Result<Self, Error> { pub fn from_js_file(file: &web_sys::File) -> Result<Self, web::JsValue> {
let name = ImString::new(file.name()); let name = ImString::new(file.name());
let size = file.size() as u64; let size = file.size() as u64;
let mime_type = ImString::new(file.type_()); let mime_type = ImString::new(file.type_());
@ -64,6 +68,7 @@ impl File {
Ok(File { name, mime_type, size, reader }) Ok(File { name, mime_type, size, reader })
} }
#[cfg(target_arch = "wasm32")]
/// Read the next chunk of file content. /// Read the next chunk of file content.
/// ///
/// If there is no more data, it returns [`None`]. /// If there is no more data, it returns [`None`].
@ -71,7 +76,7 @@ impl File {
/// The chunk size depend on the browser implementation, but it is assumed to be reasonable. /// The chunk size depend on the browser implementation, but it is assumed to be reasonable.
/// See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read and /// See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read and
/// https://github.com/w3c/FileAPI/issues/144#issuecomment-570982732. /// https://github.com/w3c/FileAPI/issues/144#issuecomment-570982732.
pub async fn read_chunk(&self) -> Result<Option<Vec<u8>>, Error> { pub async fn read_chunk(&self) -> Result<Option<Vec<u8>>, web::JsValue> {
if let Some(reader) = &*self.reader { if let Some(reader) = &*self.reader {
let js_result = JsFuture::from(reader.read()).await?; let js_result = JsFuture::from(reader.read()).await?;
let is_done = js_sys::Reflect::get(&js_result, &"done".into())?.as_bool().unwrap(); let is_done = js_sys::Reflect::get(&js_result, &"done".into())?.as_bool().unwrap();
@ -86,6 +91,12 @@ impl File {
Ok(None) Ok(None)
} }
} }
#[cfg(not(target_arch = "wasm32"))]
/// Read the next chunk of file content.
pub async fn read_chunk(&self) -> Result<Option<Vec<u8>>, web::JsValue> {
Ok(None)
}
} }
@ -105,44 +116,40 @@ type DragOverClosure = Closure<dyn Fn(web_sys::DragEvent) -> bool>;
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug)]
pub struct Manager { pub struct Manager {
#[allow(dead_code)] #[allow(dead_code)]
network: frp::Network, network: frp::Network,
files_received: frp::Source<Vec<File>>, files_received: frp::Source<Vec<File>>,
#[allow(dead_code)] #[allow(dead_code)]
drop_callback: Rc<DropClosure>, drop_handle: web::EventListenerHandle,
#[allow(dead_code)] #[allow(dead_code)]
drag_over_callback: Rc<DragOverClosure>, drag_over_handle: web::EventListenerHandle,
} }
impl Manager { impl Manager {
/// Constructor, adding listener to the given target. /// Constructor, adding listener to the given target.
pub fn new(target: &web_sys::EventTarget) -> Self { pub fn new(target: &enso_web::EventTarget) -> Self {
let logger = Logger::new("DropFileManager"); let logger = Logger::new("DropFileManager");
debug!(logger, "Creating DropFileManager"); debug!(logger, "Creating");
let network = frp::Network::new("DropFileManager"); let network = frp::Network::new("DropFileManager");
frp::extend! { network frp::extend! { network
files_received <- source(); files_received <- source();
} }
let drop: DropClosure = let drop: DropClosure =
Closure::wrap(Box::new(f!([logger,files_received](event:web_sys::DragEvent) { Closure::new(f!([logger,files_received](event:web_sys::DragEvent) {
debug!(logger, "Dropped files."); debug!(logger, "Dropped files.");
event.prevent_default(); event.prevent_default();
Self::handle_drop_event(&logger,event,&files_received) Self::handle_drop_event(&logger,event,&files_received)
}))); }));
// To mark element as a valid drop target, the `dragover` event handler should return // To mark element as a valid drop target, the `dragover` event handler should return
// `false`. See // `false`. See
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop#define_the_drop_zone // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop#define_the_drop_zone
let drag_over: DragOverClosure = Closure::wrap(Box::new(|event: web_sys::DragEvent| { let drag_over: DragOverClosure = Closure::new(|event: web_sys::DragEvent| {
event.prevent_default(); event.prevent_default();
false false
})); });
let drop_js = drop.as_ref().unchecked_ref(); let drop_handle = web::add_event_listener(target, "drop", drop);
let drag_over_js = drag_over.as_ref().unchecked_ref(); let drag_over_handle = web::add_event_listener(target, "dragover", drag_over);
target.add_event_listener_with_callback("drop", drop_js).unwrap(); Self { network, files_received, drop_handle, drag_over_handle }
target.add_event_listener_with_callback("dragover", drag_over_js).unwrap();
let drop_callback = Rc::new(drop);
let drag_over_callback = Rc::new(drag_over);
Self { network, files_received, drop_callback, drag_over_callback }
} }
/// The frp endpoint emitting signal when a file is dropped. /// The frp endpoint emitting signal when a file is dropped.
@ -161,7 +168,7 @@ impl Manager {
let files_iter = js_files_iter.filter_map(|f| match File::from_js_file(&f) { let files_iter = js_files_iter.filter_map(|f| match File::from_js_file(&f) {
Ok(file) => Some(file), Ok(file) => Some(file),
Err(err) => { Err(err) => {
error!(logger, "Error when processing dropped file: {err:?}"); error!(logger, "Error when processing dropped file: {err:?}.");
None None
} }
}); });

View File

@ -112,7 +112,7 @@ impl component::Model for Model {
} }
fn new(app: &Application, logger: &Logger) -> Self { fn new(app: &Application, logger: &Logger) -> Self {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let label = app.new_view::<text::Area>(); let label = app.new_view::<text::Area>();
let background = background::View::new(&logger); let background = background::View::new(&logger);

View File

@ -83,7 +83,7 @@ impl<M: Model, F: Frp<M>> Component<M, F> {
let logger = Logger::new(M::label()); let logger = Logger::new(M::label());
let model = Rc::new(M::new(&app, &logger)); let model = Rc::new(M::new(&app, &logger));
let frp = F::default(); let frp = F::default();
let style = StyleWatchFrp::new(&app.display.scene().style_sheet); let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
frp.init(&app, &model, &style); frp.init(&app, &model, &style);
let frp = Rc::new(frp); let frp = Rc::new(frp);
Self { frp, model, app, logger } Self { frp, model, app, logger }

View File

@ -86,7 +86,7 @@ struct Model {
impl Model { impl Model {
fn new(app: Application) -> Self { fn new(app: Application) -> Self {
let app = app.clone_ref(); let app = app.clone_ref();
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("TextLabel"); let logger = Logger::new("TextLabel");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let label = app.new_view::<text::Area>(); let label = app.new_view::<text::Area>();
@ -95,7 +95,7 @@ impl Model {
display_object.add_child(&background); display_object.add_child(&background);
display_object.add_child(&label); display_object.add_child(&label);
let style = StyleWatch::new(&app.display.scene().style_sheet); let style = StyleWatch::new(&app.display.default_scene.style_sheet);
let model = Model { background, label, display_object, style }; let model = Model { background, label, display_object, style };
model.set_layers(&scene.layers.tooltip, &scene.layers.tooltip_text); model.set_layers(&scene.layers.tooltip, &scene.layers.tooltip_text);

View File

@ -90,7 +90,7 @@ impl Entry for Label {
let display_object = display::object::Instance::new(logger); let display_object = display::object::Instance::new(logger);
let label = app.new_view::<ensogl_text::Area>(); let label = app.new_view::<ensogl_text::Area>();
let network = frp::Network::new("list_view::entry::Label"); let network = frp::Network::new("list_view::entry::Label");
let style_watch = StyleWatchFrp::new(&app.display.scene().style_sheet); let style_watch = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let color = style_watch.get_color(theme::widget::list_view::text); let color = style_watch.get_color(theme::widget::list_view::text);
let size = style_watch.get_number(theme::widget::list_view::text::size); let size = style_watch.get_number(theme::widget::list_view::text::size);

View File

@ -78,7 +78,7 @@ where E::Model: Default
let entries_range = Rc::new(CloneCell::new(default()..default())); let entries_range = Rc::new(CloneCell::new(default()..default()));
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
let provider = default(); let provider = default();
let label_layer = Rc::new(Cell::new(app.display.scene().layers.label.id())); let label_layer = Rc::new(Cell::new(app.display.default_scene.layers.label.id()));
List { logger, app, display_object, entries, entries_range, provider, label_layer } List { logger, app, display_object, entries, entries_range, provider, label_layer }
} }
@ -183,7 +183,9 @@ where E::Model: Default
/// Sets the scene layer where the labels will be placed. /// Sets the scene layer where the labels will be placed.
pub fn set_label_layer(&self, label_layer: LayerId) { pub fn set_label_layer(&self, label_layer: LayerId) {
if let Some(layer) = self.app.display.scene().layers.get_sublayer(self.label_layer.get()) { if let Some(layer) =
self.app.display.default_scene.layers.get_sublayer(self.label_layer.get())
{
for entry in &*self.entries.borrow() { for entry in &*self.entries.borrow() {
entry.entry.set_label_layer(&layer); entry.entry.set_label_layer(&layer);
} }
@ -198,7 +200,7 @@ where E::Model: Default
} }
fn create_new_entry(&self) -> DisplayedEntry<E> { fn create_new_entry(&self) -> DisplayedEntry<E> {
let layers = &self.app.display.scene().layers; let layers = &self.app.display.default_scene.layers;
let layer = layers.get_sublayer(self.label_layer.get()).unwrap_or_else(|| { let layer = layers.get_sublayer(self.label_layer.get()).unwrap_or_else(|| {
error!( error!(
self.logger, self.logger,

View File

@ -146,7 +146,7 @@ impl<E: Entry> Model<E> {
fn padding(&self) -> f32 { fn padding(&self) -> f32 {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795) // system (#795)
let styles = StyleWatch::new(&self.app.display.scene().style_sheet); let styles = StyleWatch::new(&self.app.display.default_scene.style_sheet);
styles.get_number(ensogl_hardcoded_theme::application::searcher::padding) styles.get_number(ensogl_hardcoded_theme::application::searcher::padding)
} }
@ -188,7 +188,7 @@ impl<E: Entry> Model<E> {
/// Check if the `point` is inside component assuming that it have given `size`. /// Check if the `point` is inside component assuming that it have given `size`.
fn is_inside(&self, point: Vector2<f32>, size: Vector2<f32>) -> bool { fn is_inside(&self, point: Vector2<f32>, size: Vector2<f32>) -> bool {
let pos_obj_space = let pos_obj_space =
self.app.display.scene().screen_to_object_space(&self.background, point); self.app.display.default_scene.screen_to_object_space(&self.background, point);
let x_range = (-size.x / 2.0)..=(size.x / 2.0); let x_range = (-size.x / 2.0)..=(size.x / 2.0);
let y_range = (-size.y / 2.0)..=(size.y / 2.0); let y_range = (-size.y / 2.0)..=(size.y / 2.0);
x_range.contains(&pos_obj_space.x) && y_range.contains(&pos_obj_space.y) x_range.contains(&pos_obj_space.x) && y_range.contains(&pos_obj_space.y)
@ -295,7 +295,7 @@ where E::Model: Default
let frp = &self.frp; let frp = &self.frp;
let network = &frp.network; let network = &frp.network;
let model = &self.model; let model = &self.model;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;
let view_y = DEPRECATED_Animation::<f32>::new(network); let view_y = DEPRECATED_Animation::<f32>::new(network);
let selection_y = DEPRECATED_Animation::<f32>::new(network); let selection_y = DEPRECATED_Animation::<f32>::new(network);

View File

@ -95,7 +95,7 @@ impl display::Object for ScrollArea {
impl ScrollArea { impl ScrollArea {
/// Create a new scroll area for use in the given application. /// Create a new scroll area for use in the given application.
pub fn new(app: &Application) -> ScrollArea { pub fn new(app: &Application) -> ScrollArea {
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("ScrollArea"); let logger = Logger::new("ScrollArea");
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);

View File

@ -85,7 +85,7 @@ impl Frp {
pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) { pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) {
let frp = &self; let frp = &self;
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;
let thumb_position = Animation::new(network); let thumb_position = Animation::new(network);
let thumb_color = color::Animation::new(network); let thumb_color = color::Animation::new(network);
@ -304,7 +304,7 @@ impl Scrollbar {
let app = app.clone_ref(); let app = app.clone_ref();
let model = Rc::new(Model::new(&app)); let model = Rc::new(Model::new(&app));
let frp = Frp::default(); let frp = Frp::default();
let style = StyleWatchFrp::new(&app.display.scene().style_sheet); let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
frp.init(&app, &model, &style); frp.init(&app, &model, &style);
let frp = Rc::new(frp); let frp = Rc::new(frp);
Self { frp, model, app } Self { frp, model, app }

View File

@ -58,8 +58,8 @@ impl Model {
let label_full = app.new_view::<text::Area>(); let label_full = app.new_view::<text::Area>();
let label_left = app.new_view::<text::Area>(); let label_left = app.new_view::<text::Area>();
label_full.remove_from_scene_layer(&app.display.scene().layers.main); label_full.remove_from_scene_layer(&app.display.default_scene.layers.main);
label_full.add_to_scene_layer(&app.display.scene().layers.label); label_full.add_to_scene_layer(&app.display.default_scene.layers.label);
root.add_child(&label_full); root.add_child(&label_full);
root.add_child(&label_left); root.add_child(&label_left);

View File

@ -75,24 +75,20 @@ impl Frp {
size: frp::Stream<Vector2>, size: frp::Stream<Vector2>,
mouse: &Mouse, mouse: &Mouse,
) -> Frp { ) -> Frp {
let net = &network;
let scene = &model.app.display.default_scene;
let shadow = shadow::frp_from_style(style, theme::shadow); let shadow = shadow::frp_from_style(style, theme::shadow);
let text_size = style.get_number(theme::text::size); let text_size = style.get_number(theme::text::size);
let is_dragging_left_overflow = let is_dragging_left_overflow = shape_is_dragged(net, &model.left_overflow.events, mouse);
shape_is_dragged(network, &model.left_overflow.events, mouse); let is_dragging_right_overflow = shape_is_dragged(net, &model.right_overflow.events, mouse);
let is_dragging_right_overflow = let is_dragging_track = shape_is_dragged(net, &model.track.events, mouse);
shape_is_dragged(network, &model.right_overflow.events, mouse); let is_dragging_background = shape_is_dragged(net, &model.background.events, mouse);
let is_dragging_track = shape_is_dragged(network, &model.track.events, mouse); let is_dragging_left_handle = shape_is_dragged(net, &model.track_handle_left.events, mouse);
let is_dragging_background = shape_is_dragged(network, &model.background.events, mouse);
let is_dragging_left_handle =
shape_is_dragged(network, &model.track_handle_left.events, mouse);
let is_dragging_right_handle = let is_dragging_right_handle =
shape_is_dragged(network, &model.track_handle_right.events, mouse); shape_is_dragged(net, &model.track_handle_right.events, mouse);
let background_click = relative_shape_down_position(net, scene, &model.background);
let background_click = let track_click = relative_shape_down_position(net, scene, &model.track);
relative_shape_down_position(network, model.app.display.scene(), &model.background);
let track_click =
relative_shape_down_position(network, model.app.display.scene(), &model.track);
// Initialisation of components. Required for correct layout on startup. // Initialisation of components. Required for correct layout on startup.
model.label_right.set_position_y(text_size.value() / 2.0); model.label_right.set_position_y(text_size.value() / 2.0);

View File

@ -76,7 +76,7 @@ impl NumberPicker {
let app = app.clone_ref(); let app = app.clone_ref();
let model = Rc::new(Model::new(&app)); let model = Rc::new(Model::new(&app));
let frp = number::Frp::default(); let frp = number::Frp::default();
let style = StyleWatchFrp::new(&app.display.scene().style_sheet); let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
frp.init(&app, &model, &style); frp.init(&app, &model, &style);
let frp = Rc::new(frp); let frp = Rc::new(frp);
Self { frp, model, app } Self { frp, model, app }
@ -146,7 +146,7 @@ impl NumberRangePicker {
let app = app.clone_ref(); let app = app.clone_ref();
let model = Rc::new(Model::new(&app)); let model = Rc::new(Model::new(&app));
let frp = range::Frp::default(); let frp = range::Frp::default();
let style = StyleWatchFrp::new(&app.display.scene().style_sheet); let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
frp.init(&app, &model, &style); frp.init(&app, &model, &style);
let frp = Rc::new(frp); let frp = Rc::new(frp);
Self { frp, model, app } Self { frp, model, app }

View File

@ -97,7 +97,7 @@ impl Model {
let padding = default(); let padding = default();
let app = app.clone_ref(); let app = app.clone_ref();
let scene = app.display.scene(); let scene = &app.display.default_scene;
scene.layers.add_global_shapes_order_dependency::<background::View, track::View>(); scene.layers.add_global_shapes_order_dependency::<background::View, track::View>();
scene.layers.add_global_shapes_order_dependency::<track::View, left_overflow::View>(); scene.layers.add_global_shapes_order_dependency::<track::View, left_overflow::View>();
scene.layers.add_global_shapes_order_dependency::<track::View, right_overflow::View>(); scene.layers.add_global_shapes_order_dependency::<track::View, right_overflow::View>();

View File

@ -45,7 +45,7 @@ impl Frp {
pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) { pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) {
let frp = &self; let frp = &self;
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;
model.show_background(true); model.show_background(true);
@ -55,11 +55,8 @@ impl Frp {
let track_shape_system = scene.shapes.shape_system(PhantomData::<track::Shape>); let track_shape_system = scene.shapes.shape_system(PhantomData::<track::Shape>);
track_shape_system.shape_system.set_pointer_events(false); track_shape_system.shape_system.set_pointer_events(false);
let background_click = let background_click = relative_shape_down_position(network, scene, &model.background);
relative_shape_down_position(network, model.app.display.scene(), &model.background); let track_click = relative_shape_down_position(network, scene, &model.track);
let track_click =
relative_shape_down_position(network, model.app.display.scene(), &model.track);
let style_track_color = style.get_color(theme::component::slider::track::color); let style_track_color = style.get_color(theme::component::slider::track::color);
frp::extend! { network frp::extend! { network

View File

@ -41,7 +41,7 @@ impl Frp {
pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) { pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) {
let frp = &self; let frp = &self;
let network = &frp.network; let network = &frp.network;
let scene = app.display.scene(); let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;
let base_frp = super::Frp::new(model, style, network, frp.resize.clone().into(), mouse); let base_frp = super::Frp::new(model, style, network, frp.resize.clone().into(), mouse);

View File

@ -20,7 +20,7 @@ use ensogl_core::display::shape::*;
use ensogl_core::display::style; use ensogl_core::display::style;
use ensogl_core::display::DomSymbol; use ensogl_core::display::DomSymbol;
use ensogl_core::frp; use ensogl_core::frp;
use ensogl_core::system::web::StyleSetter; use ensogl_core::system::web::traits::*;
use ensogl_hardcoded_theme as theme; use ensogl_hardcoded_theme as theme;
@ -112,14 +112,14 @@ pub fn from_shape_with_parameters_and_alpha(
} }
/// Add a theme defined box shadow to the given `DomSymbol`. /// Add a theme defined box shadow to the given `DomSymbol`.
pub fn add_to_dom_element(element: &DomSymbol, style: &StyleWatch, logger: &Logger) { pub fn add_to_dom_element(element: &DomSymbol, style: &StyleWatch) {
let off_x = style.get_number(theme::shadow::offset_x); let off_x = style.get_number(theme::shadow::offset_x);
let off_y = -style.get_number(theme::shadow::offset_y); let off_y = -style.get_number(theme::shadow::offset_y);
let alpha = style.get_number(ensogl_hardcoded_theme::shadow::html::alpha); let alpha = style.get_number(ensogl_hardcoded_theme::shadow::html::alpha);
let blur = style.get_number(ensogl_hardcoded_theme::shadow::html::blur); let blur = style.get_number(ensogl_hardcoded_theme::shadow::html::blur);
let spread = style.get_number(ensogl_hardcoded_theme::shadow::html::spread); let spread = style.get_number(ensogl_hardcoded_theme::shadow::html::spread);
let shadow = format!("{}px {}px {}px {}px rgba(0,0,0,{})", off_x, off_y, blur, spread, alpha); let shadow = format!("{}px {}px {}px {}px rgba(0,0,0,{})", off_x, off_y, blur, spread, alpha);
element.dom().set_style_or_warn("box-shadow", shadow, logger); element.dom().set_style_or_warn("box-shadow", shadow);
} }

View File

@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"]
enso-prelude = { path = "../../../../prelude"} enso-prelude = { path = "../../../../prelude"}
js-sys = { version = "0.3" } js-sys = { version = "0.3" }
nalgebra = { version = "0.26.1" } nalgebra = { version = "0.26.1" }
wasm-bindgen = { version = "0.2.58" } wasm-bindgen = { version = "0.2.78" }
[dev-dependencies] [dev-dependencies]
wasm-bindgen-test = { version = "0.3.8" } wasm-bindgen-test = { version = "0.3.8" }

View File

@ -304,7 +304,7 @@ impl Area {
fn init(self) -> Self { fn init(self) -> Self {
let network = &self.frp.network; let network = &self.frp.network;
let model = &self.data; let model = &self.data;
let scene = model.app.display.scene(); let scene = &model.app.display.default_scene;
let mouse = &scene.mouse.frp; let mouse = &scene.mouse.frp;
let input = &self.frp.input; let input = &self.frp.input;
let out = &self.frp.output; let out = &self.frp.output;
@ -532,7 +532,7 @@ impl Area {
fn symbols(&self) -> SmallVec<[display::Symbol; 1]> { fn symbols(&self) -> SmallVec<[display::Symbol; 1]> {
let text_symbol = self.data.glyph_system.sprite_system().symbol.clone_ref(); let text_symbol = self.data.glyph_system.sprite_system().symbol.clone_ref();
let shapes = &self.data.app.display.scene().shapes; let shapes = &self.data.app.display.default_scene.shapes;
let selection_system = shapes.shape_system(PhantomData::<selection::shape::Shape>); let selection_system = shapes.shape_system(PhantomData::<selection::shape::Shape>);
let _selection_symbol = selection_system.shape_system.symbol.clone_ref(); let _selection_symbol = selection_system.shape_system.symbol.clone_ref();
//TODO[ao] we cannot move selection symbol, as it is global for all the text areas. //TODO[ao] we cannot move selection symbol, as it is global for all the text areas.
@ -568,7 +568,7 @@ impl AreaModel {
/// Constructor. /// Constructor.
pub fn new(app: &Application, frp_endpoints: &FrpEndpoints) -> Self { pub fn new(app: &Application, frp_endpoints: &FrpEndpoints) -> Self {
let app = app.clone_ref(); let app = app.clone_ref();
let scene = app.display.scene(); let scene = &app.display.default_scene;
let logger = Logger::new("text_area"); let logger = Logger::new("text_area");
let selection_map = default(); let selection_map = default();
let fonts = scene.extension::<typeface::font::Registry>(); let fonts = scene.extension::<typeface::font::Registry>();
@ -703,7 +703,7 @@ impl AreaModel {
let origin_clip_space = camera.view_projection_matrix() * origin_world_space; let origin_clip_space = camera.view_projection_matrix() * origin_world_space;
let inv_object_matrix = self.transform_matrix().try_inverse().unwrap(); let inv_object_matrix = self.transform_matrix().try_inverse().unwrap();
let shape = self.app.display.scene().frp.shape.value(); let shape = self.app.display.default_scene.frp.shape.value();
let clip_space_z = origin_clip_space.z; let clip_space_z = origin_clip_space.z;
let clip_space_x = origin_clip_space.w * 2.0 * screen_pos.x / shape.width; let clip_space_x = origin_clip_space.w * 2.0 * screen_pos.x / shape.width;
let clip_space_y = origin_clip_space.w * 2.0 * screen_pos.y / shape.height; let clip_space_y = origin_clip_space.w * 2.0 * screen_pos.y / shape.height;

View File

@ -102,7 +102,9 @@ impl System {
let logger = Logger::new("glyph_system"); let logger = Logger::new("glyph_system");
let size = font::msdf::Texture::size(); let size = font::msdf::Texture::size();
let scene = scene.as_ref(); let scene = scene.as_ref();
let context = scene.context.clone_ref(); // FIXME: The following line is unsafe. It can fail if the context was lost before calling
// this function. Also, the texture will not be restored after context restoration.
let context = scene.context.borrow().as_ref().unwrap().clone_ref();
let sprite_system = SpriteSystem::new(scene); let sprite_system = SpriteSystem::new(scene);
let symbol = sprite_system.symbol(); let symbol = sprite_system.symbol();
let texture = Texture::new(&context, (0, 0)); let texture = Texture::new(&context, (0, 0));

View File

@ -45,7 +45,7 @@ typenum = { version = "1.11.2" }
# We require exact version of wasm-bindgen because we do patching final js in our build process, # We require exact version of wasm-bindgen because we do patching final js in our build process,
# and this is vulnerable to any wasm-bindgen version change. # and this is vulnerable to any wasm-bindgen version change.
wasm-bindgen = { version = "0.2.58", features = ["nightly"] } wasm-bindgen = { version = "0.2.78", features = ["nightly"] }
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3.4" version = "0.3.4"

View File

@ -1,12 +1,11 @@
//! This module contains implementation of `DynamicLoop`, a loop manager which runs a //! This module contains implementation of loops mainly used for per-frame callbacks firing.
//! `DynamicLoopCallback` once per frame.
use crate::prelude::*; use crate::prelude::*;
use crate::control::callback;
use crate::system::web; use crate::system::web;
use crate::system::web::traits::*;
use wasm_bindgen::prelude::Closure; use web::Closure;
@ -70,7 +69,9 @@ where Callback: RawLoopCallback
let weak_data = Rc::downgrade(&data); let weak_data = Rc::downgrade(&data);
let on_frame = move |time| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time)); let on_frame = move |time| weak_data.upgrade().for_each(|t| t.borrow_mut().run(time));
data.borrow_mut().on_frame = Some(Closure::new(on_frame)); data.borrow_mut().on_frame = Some(Closure::new(on_frame));
let handle_id = web::request_animation_frame(data.borrow_mut().on_frame.as_ref().unwrap()); let handle_id = web::window.request_animation_frame_with_closure_or_panic(
data.borrow_mut().on_frame.as_ref().unwrap(),
);
data.borrow_mut().handle_id = handle_id; data.borrow_mut().handle_id = handle_id;
Self { data } Self { data }
} }
@ -100,14 +101,14 @@ impl<Callback> RawLoopData<Callback> {
let callback = &mut self.callback; let callback = &mut self.callback;
self.handle_id = self.on_frame.as_ref().map_or(default(), |on_frame| { self.handle_id = self.on_frame.as_ref().map_or(default(), |on_frame| {
callback(current_time_ms as f32); callback(current_time_ms as f32);
web::request_animation_frame(on_frame) web::window.request_animation_frame_with_closure_or_panic(on_frame)
}) })
} }
} }
impl<Callback> Drop for RawLoopData<Callback> { impl<Callback> Drop for RawLoopData<Callback> {
fn drop(&mut self) { fn drop(&mut self) {
web::cancel_animation_frame(self.handle_id); web::window.cancel_animation_frame_or_panic(self.handle_id);
} }
} }
@ -238,71 +239,3 @@ where Callback: LoopCallback
Self::new(FixedFrameRateSampler::new(frame_rate, callback)) Self::new(FixedFrameRateSampler::new(frame_rate, callback))
} }
} }
// ===================
// === DynamicLoop ===
// ===================
/// A callback to register in DynamicLoop, taking time_ms:f32 as its input.
pub trait DynamicLoopCallback = callback::CopyCallbackMut1Fn<TimeInfo>;
/// Animation loop which allows registering and unregistering callbacks dynamically. After a
/// callback is registered, a `callback::Handle` is returned. The callback is automatically removed
/// as soon as its handle is dropped. You can also use the `forget` method on the handle to make the
/// callback registered forever, but beware that it can easily lead to memory leaks.
///
/// Please refer to `Loop` if you don't need the ability to add / remove callbacks dynamically and
/// you want better performance.
#[derive(Clone, CloneRef, Debug)]
pub struct DynamicLoop {
raw_loop: Loop<Box<dyn FnMut(TimeInfo)>>,
data: Rc<RefCell<DynamicLoopData>>,
}
/// Internal representation for `DynamicLoop`.
#[derive(Debug, Default)]
pub struct DynamicLoopData {
on_frame: callback::CopyRegistry1<TimeInfo>,
on_before_frame: callback::CopyRegistry1<TimeInfo>,
on_after_frame: callback::CopyRegistry1<TimeInfo>,
}
impl Default for DynamicLoop {
fn default() -> Self {
let data = Rc::new(RefCell::new(DynamicLoopData::default()));
let weak = Rc::downgrade(&data);
let raw_loop: Loop<Box<dyn FnMut(TimeInfo)>> = Loop::new(Box::new(move |time| {
weak.upgrade().for_each(|data| {
let mut data_mut = data.borrow_mut();
data_mut.on_before_frame.run_all(time);
data_mut.on_frame.run_all(time);
data_mut.on_after_frame.run_all(time);
})
}));
Self { raw_loop, data }
}
}
impl DynamicLoop {
/// Constructor.
pub fn new() -> Self {
default()
}
/// Add new callback which will be run on every animation frame.
pub fn on_frame<F: DynamicLoopCallback>(&self, callback: F) -> callback::Handle {
self.data.borrow_mut().on_frame.add(Box::new(callback))
}
/// Add new callback which will be run on before all callbacks registered with `on_frame`.
pub fn on_before_frame<F: DynamicLoopCallback>(&self, callback: F) -> callback::Handle {
self.data.borrow_mut().on_before_frame.add(Box::new(callback))
}
/// Add new callback which will be run on after all callbacks registered with `on_frame`.
pub fn on_after_frame<F: DynamicLoopCallback>(&self, callback: F) -> callback::Handle {
self.data.borrow_mut().on_after_frame.add(Box::new(callback))
}
}

View File

@ -12,11 +12,12 @@ use crate::prelude::*;
use crate::control::callback; use crate::control::callback;
use crate::display; use crate::display;
use crate::display::scene::DomPath;
use crate::display::style::theme; use crate::display::style::theme;
use crate::display::world::World; use crate::display::world::World;
use crate::gui::cursor::Cursor; use crate::gui::cursor::Cursor;
use crate::system::web; use crate::system::web;
use enso_web::StyleSetter; use enso_web::traits::*;
@ -41,19 +42,20 @@ pub struct Application {
impl Application { impl Application {
/// Constructor. /// Constructor.
pub fn new(dom: &web_sys::HtmlElement) -> Self { pub fn new(dom: impl DomPath) -> Self {
let logger = Logger::new("Application"); let logger = Logger::new("Application");
let display = World::new(dom); let display = World::new();
let scene = display.scene(); let scene = &display.default_scene;
scene.display_in(dom);
let commands = command::Registry::create(&logger); let commands = command::Registry::create(&logger);
let shortcuts = let shortcuts =
shortcut::Registry::new(&logger, &scene.mouse.frp, &scene.keyboard.frp, &commands); shortcut::Registry::new(&logger, &scene.mouse.frp, &scene.keyboard.frp, &commands);
let views = view::Registry::create(&logger, &display, &commands, &shortcuts); let views = view::Registry::create(&logger, &display, &commands, &shortcuts);
let themes = theme::Manager::from(&display.scene().style_sheet); let themes = theme::Manager::from(&display.default_scene.style_sheet);
let cursor = Cursor::new(display.scene()); let cursor = Cursor::new(&display.default_scene);
display.add_child(&cursor); display.add_child(&cursor);
web::body().set_style_or_panic("cursor", "none"); web::document.body_or_panic().set_style_or_warn("cursor", "none");
let update_themes_handle = display.on_before_frame(f_!(themes.update())); let update_themes_handle = display.on.before_frame.add(f_!(themes.update()));
Self { logger, cursor, display, commands, shortcuts, views, themes, update_themes_handle } Self { logger, cursor, display, commands, shortcuts, views, themes, update_themes_handle }
} }
@ -74,3 +76,18 @@ impl AsRef<theme::Manager> for Application {
&self.themes &self.themes
} }
} }
// =============
// === Tests ===
// =============
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn native_compilation_in_test_mode() {
let _app = Application::new("root");
}
}

View File

@ -116,73 +116,82 @@ impl ArgReader for bool {
#[macro_export] #[macro_export]
macro_rules! read_args { macro_rules! read_args {
([$($($path:tt)*).*] { $($field:ident : $field_type:ty),* $(,)? }) => { ([$($($path:tt)*).*] { $($field:ident : $field_type:ty),* $(,)? }) => {
mod _READ_ARGS {
use super::*;
use $crate::prelude::*;
use $crate::system::web::traits::*;
/// Reflection mechanism containing string representation of option names. /// Reflection mechanism containing string representation of option names.
#[derive(Clone,Copy,Debug,Default)] #[derive(Clone,Copy,Debug,Default)]
pub struct ArgNames; pub struct ArgNames;
impl ArgNames { impl ArgNames {
$( $(
/// Name of the field. /// Name of the field.
pub fn $field(&self) -> &'static str { pub fn $field(&self) -> &'static str {
stringify!{$field} stringify!{$field}
}
)*
}
/// The structure containing application configs.
#[derive(Clone,Debug,Default)]
#[allow(missing_docs)]
pub struct Args {
$(pub $field : Option<$field_type>),*
}
impl Args {
/// Constructor.
fn new() -> Self {
let logger = Logger::new(stringify!{Args});
let window = web::window();
let path = vec![$($($path)*),*];
match web::reflect_get_nested_object(&window,&path).ok() {
None => {
let path = path.join(".");
error!(&logger,"The config path '{path}' is invalid.");
default()
} }
Some(cfg) => { )*
let keys = web::object_keys(&cfg);
let mut keys = keys.into_iter().collect::<HashSet<String>>();
$(
let name = stringify!{$field};
let tp = stringify!{$field_type};
let $field = web::reflect_get_nested_string(&cfg,&[name]).ok();
let $field = $field.map($crate::application::args::ArgReader::read_arg);
if $field == Some(None) {
warning!(&logger,"Failed to convert the argument '{name}' value \
to the '{tp}' type.");
}
let $field = $field.flatten();
keys.remove(name);
)*
for key in keys {
warning!(&logger,"Unknown config option provided '{key}'.");
}
Self {$($field),*}
}
}
} }
/// This is a dummy function which initializes the arg reading process. This function /// The structure containing application configs.
/// does nothing, however, in order to call it, the user would need to access a field in #[derive(Clone,Debug,Default)]
/// the lazy static variable `ARGS`, which would trigger argument parsing process. #[allow(missing_docs)]
pub fn init(&self) {} pub struct Args {
$(pub $field : Option<$field_type>),*
}
/// Reflection mechanism to get string representation of argument names. impl Args {
pub fn names(&self) -> ArgNames { ArgNames } /// Constructor.
} fn new() -> Self {
let logger = Logger::new(stringify!{Args});
let path = vec![$($($path)*),*];
match ensogl::system::web::Reflect::get_nested_object
(&ensogl::system::web::window,&path).ok() {
None => {
let path = path.join(".");
error!(&logger,"The config path '{path}' is invalid.");
default()
}
Some(cfg) => {
let keys = ensogl::system::web::Object::keys_vec(&cfg);
let mut keys = keys.into_iter().collect::<HashSet<String>>();
$(
let name = stringify!{$field};
let tp = stringify!{$field_type};
let $field = ensogl::system::web::Reflect::
get_nested_object_printed_as_string(&cfg,&[name]).ok();
let $field = $field.map
($crate::application::args::ArgReader::read_arg);
if $field == Some(None) {
warning!(&logger,"Failed to convert the argument '{name}' \
value to the '{tp}' type.");
}
let $field = $field.flatten();
keys.remove(name);
)*
for key in keys {
warning!(&logger,"Unknown config option provided '{key}'.");
}
Self {$($field),*}
}
}
}
lazy_static! { /// This is a dummy function which initializes the arg reading process. This
/// Application arguments initialized in a lazy way (on first read). /// function does nothing, however, in order to call it, the user would need to
pub static ref ARGS : Args = Args::new(); /// access a field in the lazy static variable `ARGS`, which would trigger argument
/// parsing process.
pub fn init(&self) {}
/// Reflection mechanism to get string representation of argument names.
pub fn names(&self) -> ArgNames { ArgNames }
}
lazy_static! {
/// Application arguments initialized in a lazy way (on first read).
pub static ref ARGS : Args = Args::new();
}
} }
pub use _READ_ARGS::*;
}; };
} }

View File

@ -5,92 +5,50 @@ use crate::prelude::*;
pub mod event; pub mod event;
use crate::control::callback; use crate::control::callback;
use crate::control::callback::traits::*;
use crate::system::web; use crate::system::web;
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use wasm_bindgen::prelude::Closure; use web::Closure;
use wasm_bindgen::JsCast; use web::JsCast;
use wasm_bindgen::JsValue; use web::JsValue;
pub use crate::frp::io::mouse::*; pub use crate::frp::io::mouse::*;
pub use event::*; pub use event::*;
// =======================
// === EventDispatcher ===
// =======================
// TODO: Consider merging this implementation with crate::control::callback::* ones.
/// Shared event dispatcher.
#[derive(Debug, CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Default(bound = ""))]
pub struct EventDispatcher<T> {
rc: Rc<RefCell<callback::Registry1<T>>>,
}
impl<T> EventDispatcher<T> {
/// Adds a new callback.
pub fn add<F: FnMut(&T) + 'static>(&self, f: F) -> callback::Handle {
self.rc.borrow_mut().add(f)
}
/// Dispatches event to all callbacks.
pub fn dispatch(&self, t: &T) {
self.rc.borrow_mut().run_all(t);
}
}
// ==================== // ====================
// === MouseManager === // === MouseManager ===
// ==================== // ====================
/// An utility which registers JavaScript handlers for mouse events and translates them to Rust /// A utility which registers JavaScript handlers for mouse events and translates them to Rust
/// handlers. It is a top level mouse registry hub. /// handlers. It is a top level mouse registry hub.
#[derive(Clone, CloneRef, Debug, Shrinkwrap)] #[derive(Clone, CloneRef, Debug, Shrinkwrap)]
pub struct MouseManager { pub struct MouseManager {
#[shrinkwrap(main_field)] #[shrinkwrap(main_field)]
dispatchers: MouseManagerDispatchers, dispatchers: MouseManagerDispatchers,
closures: Rc<MouseManagerClosures>, handles: Rc<MouseManagerEventListenerHandles>,
dom: web::dom::WithKnownShape<web::EventTarget>, dom: web::dom::WithKnownShape<web::EventTarget>,
} }
/// A JavaScript callback closure for any mouse event. /// A JavaScript callback closure for any mouse event.
pub type MouseEventJsClosure = Closure<dyn Fn(JsValue)>; pub type MouseEventJsClosure = Closure<dyn FnMut(JsValue)>;
macro_rules! define_bindings { macro_rules! define_bindings {
( $( $js_event:ident :: $js_name:ident => $name:ident ($target:ident) ),* $(,)? ) => { ( $( $js_event:ident :: $js_name:ident => $name:ident ($target:ident) ),* $(,)? ) => {
/// Keeps references to JavaScript closures in order to keep them alive. /// Keeps references to JavaScript closures in order to keep them alive.
#[derive(Debug)] #[derive(Debug)]
pub struct MouseManagerClosures { pub struct MouseManagerEventListenerHandles {
target : web::EventTarget, $($name : web::EventListenerHandle),*
$($name : MouseEventJsClosure),*
}
impl Drop for MouseManagerClosures {
fn drop(&mut self) {
$(
let target = &self.target;
let js_closure = self.$name.as_ref().unchecked_ref();
let js_name = stringify!($js_name);
let result = target.remove_event_listener_with_callback(js_name,js_closure);
if let Err(e) = result { panic!("Cannot add event listener. {:?}",e) }
)*
}
} }
/// Set of dispatchers for various mouse events. /// Set of dispatchers for various mouse events.
#[derive(Clone,CloneRef,Debug,Default)] #[derive(Clone,CloneRef,Debug,Default)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct MouseManagerDispatchers { pub struct MouseManagerDispatchers {
$(pub $name : EventDispatcher<$target>),* $(pub $name : callback::registry::RefMut1<$target>),*
} }
impl MouseManager { impl MouseManager {
@ -101,41 +59,36 @@ macro_rules! define_bindings {
/// Constructor which takes the exact element to set listener as a separate argument. /// Constructor which takes the exact element to set listener as a separate argument.
/// ///
/// Sometimes we want to listen for mouse event for element without ResizeObserver. Thus /// Sometimes we want to listen for mouse event for element without ResizeObserver.
/// some html element may be passed as a size provider, and another one where we attach /// Thus, some html element may be passed as a size provider, and another one where we
/// listeners (for example `body` and `window` respectively). /// attach listeners (for example `body` and `window` respectively).
pub fn new_separated pub fn new_separated
(dom:&web::dom::WithKnownShape<web::EventTarget>,target:&web::EventTarget) -> Self { (dom:&web::dom::WithKnownShape<web::EventTarget>,target:&web::EventTarget) -> Self {
let dispatchers = MouseManagerDispatchers::default(); let dispatchers = MouseManagerDispatchers::default();
let dom = dom.clone(); let dom = dom.clone();
let target = target.clone();
$( $(
let shape = dom.shape.clone_ref(); let shape = dom.shape.clone_ref();
let dispatcher = dispatchers.$name.clone_ref(); let dispatcher = dispatchers.$name.clone_ref();
let $name : MouseEventJsClosure = Closure::wrap(Box::new(move |event:JsValue| { let closure : MouseEventJsClosure = Closure::new(move |event:JsValue| {
let shape = shape.value(); let shape = shape.value();
let event = event.unchecked_into::<web_sys::$js_event>(); let event = event.unchecked_into::<web::$js_event>();
dispatcher.dispatch(&event::$target::new(event,shape)) dispatcher.run_all(&event::$target::new(event,shape))
})); });
let js_closure = $name.as_ref().unchecked_ref(); let js_name = stringify!($js_name);
let js_name = stringify!($js_name); let opt = event_listener_options();
let options = event_listener_options(); let $name = web::add_event_listener_with_options(&target,js_name,closure,&opt);
let result =
target.add_event_listener_with_callback_and_add_event_listener_options
(js_name,js_closure,&options);
if let Err(e) = result { panic!("Cannot add event listener. {:?}",e) }
)* )*
let closures = Rc::new(MouseManagerClosures {target,$($name),*}); let handles = Rc::new(MouseManagerEventListenerHandles {$($name),*});
Self {dispatchers,closures,dom} Self {dispatchers,handles,dom}
} }
} }
}; };
} }
/// Retrun options for addEventListener function. See also /// Return options for addEventListener function. See also
/// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener /// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
fn event_listener_options() -> web_sys::AddEventListenerOptions { fn event_listener_options() -> web::AddEventListenerOptions {
let mut options = web_sys::AddEventListenerOptions::new(); let mut options = web::AddEventListenerOptions::new();
// We listen for events in capture phase, so we can decide ourself if it should be passed // We listen for events in capture phase, so we can decide ourself if it should be passed
// further. // further.
options.capture(true); options.capture(true);

View File

@ -2,10 +2,11 @@
use crate::prelude::*; use crate::prelude::*;
use crate::system::web::dom::Shape; use crate::system::web;
use enso_frp::io::mouse; use enso_frp::io::mouse;
use wasm_bindgen::JsCast; use web::dom::Shape;
use web::traits::*;
@ -19,13 +20,13 @@ macro_rules! define_events {
#[derive(Debug,Clone,From,Shrinkwrap)] #[derive(Debug,Clone,From,Shrinkwrap)]
pub struct $name { pub struct $name {
#[shrinkwrap(main_field)] #[shrinkwrap(main_field)]
raw : web_sys::$js_event, raw : web::$js_event,
shape : Shape, shape : Shape,
} }
impl $name { impl $name {
/// Constructor. /// Constructor.
pub fn new(raw:web_sys::$js_event,shape:Shape) -> Self { pub fn new(raw:web::$js_event,shape:Shape) -> Self {
Self {raw,shape} Self {raw,shape}
} }
@ -66,15 +67,15 @@ macro_rules! define_events {
/// Return the event handler that caught this event if it exists and if it is an /// Return the event handler that caught this event if it exists and if it is an
/// html element. Returns `None` if the event was caught, for example, byt the window. /// html element. Returns `None` if the event was caught, for example, byt the window.
fn try_get_current_target_element(&self) -> Option<web_sys::Element> { fn try_get_current_target_element(&self) -> Option<web::Element> {
let target = self.current_target()?; let target = self.current_target()?;
target.value_of().dyn_into::<web_sys::Element>().ok() target.value_of().dyn_into::<web::Element>().ok()
} }
/// Return the position relative to the given element. /// Return the position relative to the given element.
/// ///
/// Note: causes reflow of the JS layout. /// Note: causes reflow of the JS layout.
pub fn relative_position_with_reflow(&self, element:&web_sys::Element) -> Vector2<f32> { pub fn relative_position_with_reflow(&self, element:&web::Element) -> Vector2<f32> {
let rect = element.get_bounding_client_rect(); let rect = element.get_bounding_client_rect();
let x = self.client_x() as f64 - rect.left(); let x = self.client_x() as f64 - rect.left();
let y = self.client_y() as f64 - rect.top(); let y = self.client_y() as f64 - rect.top();
@ -83,9 +84,9 @@ macro_rules! define_events {
} }
impl AsRef<web_sys::Event> for $name { impl AsRef<web::Event> for $name {
fn as_ref(&self) -> &web_sys::Event { fn as_ref(&self) -> &web::Event {
let js_event = AsRef::<web_sys::$js_event>::as_ref(self); let js_event = AsRef::<web::$js_event>::as_ref(self);
js_event.as_ref() js_event.as_ref()
} }
} }

View File

@ -4,14 +4,12 @@ use crate::prelude::*;
use crate::debug::stats::StatsData; use crate::debug::stats::StatsData;
use crate::system::web; use crate::system::web;
use crate::system::web::StyleSetter; use crate::system::web::traits::*;
use crate::system::web::JsValue;
use num_traits::cast::AsPrimitive; use num_traits::cast::AsPrimitive;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::f64; use std::f64;
use wasm_bindgen;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
@ -105,7 +103,7 @@ impl Default for Config {
impl Config { impl Config {
/// Translates the configuration to JS values. /// Translates the configuration to JS values.
pub fn to_js_config(&self) -> SamplerConfig { pub fn to_js_config(&self) -> SamplerConfig {
let ratio = web::window().device_pixel_ratio(); let ratio = web::window.device_pixel_ratio();
SamplerConfig { SamplerConfig {
background_color: (&self.background_color).into(), background_color: (&self.background_color).into(),
label_color_ok: (&self.label_color_ok).into(), label_color_ok: (&self.label_color_ok).into(),
@ -165,19 +163,19 @@ impl DomData {
/// Constructor. /// Constructor.
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
pub fn new() -> Self { pub fn new() -> Self {
let root = web::create_div(); let root = web::document.create_div_or_panic();
root.set_class_name("performance-monitor"); root.set_class_name("performance-monitor");
root.set_style_or_panic("position", "absolute"); root.set_style_or_warn("position", "absolute");
root.set_style_or_panic("z-index", "100"); root.set_style_or_warn("z-index", "100");
root.set_style_or_panic("left", "8px"); root.set_style_or_warn("left", "8px");
root.set_style_or_panic("top", "8px"); root.set_style_or_warn("top", "8px");
root.set_style_or_panic("overflow", "hidden"); root.set_style_or_warn("overflow", "hidden");
root.set_style_or_panic("border-radius", "6px"); root.set_style_or_warn("border-radius", "6px");
root.set_style_or_panic("box-shadow", "0px 0px 20px -4px rgba(0,0,0,0.44)"); root.set_style_or_warn("box-shadow", "0px 0px 20px -4px rgba(0,0,0,0.44)");
web::body().prepend_with_node_1(&root).unwrap(); web::document.body_or_panic().prepend_with_node_1(&root).unwrap();
let canvas = web::create_canvas(); let canvas = web::document.create_canvas_or_panic();
canvas.set_style_or_panic("display", "block"); canvas.set_style_or_warn("display", "block");
let context = canvas.get_context("2d").unwrap().unwrap(); let context = canvas.get_context("2d").unwrap().unwrap();
let context: web::CanvasRenderingContext2d = context.dyn_into().unwrap(); let context: web::CanvasRenderingContext2d = context.dyn_into().unwrap();
@ -337,7 +335,7 @@ impl Renderer {
fn resize(&mut self) { fn resize(&mut self) {
if let Some(dom) = &self.dom { if let Some(dom) = &self.dom {
let ratio = web::window().device_pixel_ratio(); let ratio = web::window.device_pixel_ratio();
let width = self.config.labels_width let width = self.config.labels_width
+ self.config.results_width + self.config.results_width
+ self.config.plots_width + self.config.plots_width
@ -355,8 +353,8 @@ impl Renderer {
self.height = height; self.height = height;
dom.canvas.set_width(u_width); dom.canvas.set_width(u_width);
dom.canvas.set_height(u_height); dom.canvas.set_height(u_height);
dom.canvas.set_style_or_panic("width", format!("{}px", width / ratio)); dom.canvas.set_style_or_warn("width", format!("{}px", width / ratio));
dom.canvas.set_style_or_panic("height", format!("{}px", height / ratio)); dom.canvas.set_style_or_warn("height", format!("{}px", height / ratio));
} }
} }

View File

@ -4,6 +4,7 @@
use crate::prelude::*; use crate::prelude::*;
use crate::control::callback; use crate::control::callback;
use crate::control::callback::traits::*;
use crate::data::dirty; use crate::data::dirty;
use crate::data::dirty::traits::*; use crate::data::dirty::traits::*;
use crate::display; use crate::display;
@ -158,10 +159,10 @@ impl Default for Matrix {
// ==================== // ====================
/// Function used to return the updated screen dimensions. /// Function used to return the updated screen dimensions.
pub trait ScreenUpdateFn = callback::CallbackMut1Fn<Vector2<f32>>; pub trait ScreenUpdateFn = Fn(Vector2<f32>) + 'static;
/// Function used to return the updated `Camera2d`'s zoom. /// Function used to return the updated `Camera2d`'s zoom.
pub trait ZoomUpdateFn = callback::CallbackMut1Fn<f32>; pub trait ZoomUpdateFn = Fn(f32) + 'static;
/// Internal `Camera2d` representation. Please see `Camera2d` for full documentation. /// Internal `Camera2d` representation. Please see `Camera2d` for full documentation.
#[derive(Debug)] #[derive(Debug)]
@ -174,8 +175,8 @@ struct Camera2dData {
clipping: Clipping, clipping: Clipping,
matrix: Matrix, matrix: Matrix,
dirty: Dirty, dirty: Dirty,
zoom_update_registry: callback::Registry1<f32>, zoom_update_registry: callback::registry::CopyMut1<f32>,
screen_update_registry: callback::Registry1<Vector2<f32>>, screen_update_registry: callback::registry::CopyMut1<Vector2<f32>>,
} }
type ProjectionDirty = dirty::SharedBool<()>; type ProjectionDirty = dirty::SharedBool<()>;
@ -271,8 +272,7 @@ impl Camera2dData {
} }
if changed { if changed {
self.matrix.view_projection = self.matrix.projection * self.matrix.view; self.matrix.view_projection = self.matrix.projection * self.matrix.view;
let zoom = self.zoom; self.zoom_update_registry.run_all(self.zoom);
self.zoom_update_registry.run_all(&zoom);
} }
changed changed
} }
@ -311,7 +311,7 @@ impl Camera2dData {
_ => unimplemented!(), _ => unimplemented!(),
}; };
let dimensions = Vector2::new(width, height); let dimensions = Vector2::new(width, height);
self.screen_update_registry.run_all(&dimensions); self.screen_update_registry.run_all(dimensions);
} }
fn reset_zoom(&mut self) { fn reset_zoom(&mut self) {
@ -406,6 +406,8 @@ impl Camera2d {
self.data.borrow_mut().update(scene) self.data.borrow_mut().update(scene)
} }
// FIXME: This can fail, for example, when during calling the callback another callback is
// being registered.
/// Adds a callback to notify when `zoom` is updated. /// Adds a callback to notify when `zoom` is updated.
pub fn add_zoom_update_callback<F: ZoomUpdateFn>(&self, f: F) -> callback::Handle { pub fn add_zoom_update_callback<F: ZoomUpdateFn>(&self, f: F) -> callback::Handle {
self.data.borrow_mut().add_zoom_update_callback(f) self.data.borrow_mut().add_zoom_update_callback(f)

View File

@ -91,18 +91,18 @@ impl NavigatorModel {
let distance_to_zoom_factor_of_1 = distance_to_zoom_factor_of_1(&camera); let distance_to_zoom_factor_of_1 = distance_to_zoom_factor_of_1(&camera);
let pan_speed = pan_speed.get().into_on().unwrap_or(0.0); let pan_speed = pan_speed.get().into_on().unwrap_or(0.0);
let movement_scale_for_distance = distance / distance_to_zoom_factor_of_1; let movement_scale_for_distance = distance / distance_to_zoom_factor_of_1;
let diff = pan_speed * Vector3::new(pan.movement.x, pan.movement.y, 0.0) * movement_scale_for_distance; let movement = Vector3::new(pan.movement.x, pan.movement.y, 0.0);
let diff = pan_speed * movement * movement_scale_for_distance;
simulator.update_target_value(|p| p - diff); simulator.update_target_value(|p| p - diff);
}); });
let resize_callback = camera.add_screen_update_callback( let resize_callback =
enclose!((mut simulator,camera) move |_:&Vector2<f32>| { camera.add_screen_update_callback(enclose!((mut simulator,camera) move |_| {
let position = camera.position(); let position = camera.position();
simulator.set_value(position); simulator.set_value(position);
simulator.set_target_value(position); simulator.set_target_value(position);
simulator.set_velocity(default()); simulator.set_velocity(default());
}), }));
);
let zoom_callback = f!([camera,simulator,max_zoom] (zoom:ZoomEvent) { let zoom_callback = f!([camera,simulator,max_zoom] (zoom:ZoomEvent) {
let point = zoom.focus; let point = zoom.focus;

View File

@ -183,21 +183,7 @@ fn on_dirty_callback(f: &Rc<RefCell<Box<dyn Fn()>>>) -> OnDirtyCallback {
/// A hierarchical representation of object containing information about transformation in 3D space, /// A hierarchical representation of object containing information about transformation in 3D space,
/// list of children, and set of utils for dirty flag propagation. /// list of children, and set of utils for dirty flag propagation.
/// ///
/// ## Host /// See the documentation of [`Instance`] to learn more.
/// The model is parametrized with a `Host`. In real life use cases, host will be instantiated with
/// `Scene`. For the needs of tests, its often instantiated with empty tuple for simplicity. Host
/// has a very important role in decoupling the architecture. You need to provide the `update`
/// method with a reference to the host, which is then passed to `on_show` and `on_hide` callbacks
/// when a particular display objects gets shown or hidden respectively. This can be used for a
/// dynamic management of GPU-side sprites. For example, after adding a display object to a scene,
/// a new sprites can be created to display it visually. After removing the objects, and adding it
/// to a different scene (second GPU context), the sprites in the first context can be removed, and
/// new sprites in the new context can be created. Thus, abstracting over `Host` allows users of
/// this library to define a view model (like few sliders in a box) without the need to contain
/// reference to a particular renderer, and attach the renderer on-demand, when the objects will be
/// placed on the stage.
///
/// Please note, that this functionality is fairly new, and the library do not use it like this yet.
#[derive(Derivative)] #[derive(Derivative)]
#[derivative(Debug(bound = ""))] #[derivative(Debug(bound = ""))]
pub struct Model<Host = Scene> { pub struct Model<Host = Scene> {
@ -610,18 +596,33 @@ pub struct Id(usize);
/// list of children, and set of utils for dirty flag propagation. /// list of children, and set of utils for dirty flag propagation.
/// ///
/// ## Host /// ## Host
/// The structure is parametrized with a `Host`. In real life use cases, host will be instantiated /// The model is parametrized with a `Host`. In real life use cases, host is **ALWAYS** instantiated
/// with [`Scene`]. For simplicity, it is instantiated to empty tuple in tests. Host has a very /// with `Scene`. For the needs of tests, its often instantiated with empty tuple for simplicity.
/// important role in decoupling the architecture. You need to provide the `update` method with a /// Host has a very important role in decoupling the architecture. You need to provide the `update`
/// reference to the host, which is then passed to `on_show` and `on_hide` callbacks when a /// method with a reference to the host, which is then passed to `on_show` and `on_hide` callbacks
/// particular display objects gets shown or hidden respectively. This can be used for a dynamic /// when a particular display objects gets shown or hidden respectively.
/// management of GPU-side sprites. For example, after adding a display object to a scene, a new ///
/// sprites can be created to display it visually. After removing the objects, and adding it to a /// This can be used for a dynamic management of GPU-side rendering. After adding a display object
/// different scene (second GPU context), the sprites in the first context can be removed, and new /// to a scene, a new sprite can be created to display it visually. After removing the object and
/// sprites in the new context can be created. Thus, abstracting over `Host` allows users of this /// adding it to a different scene (another GPU context), the sprite in the first context can be
/// library to define a view model (like few sliders in a box) without the need to contain reference /// trashed, and a new sprite in the new context can be created instead. This mechanism is currently
/// to a particular renderer, and attach the renderer on-demand, when the objects will be placed on /// also used for sprites layer management, but this functionality is inherently connected to the
/// the stage. /// sprites creation in a given context (moving object to a different layer of the same [`Scene`] is
/// similar to moving it to a layer of a different [`Scene`].
///
/// Thus, abstracting over `Host` allows users of this library to define a view model (like few
/// sliders in a box) without the need to contain reference to a particular renderer, and attach the
/// renderer on-demand, when the objects will be placed on the stage.
///
/// Please note, that moving an object between two Scenes (two WebGL contexts) is not implemented
/// yet.
///
/// ## Possible changes to the Host parametrization design
/// Instead of parametrizing the Display Object, the [`Host`] could be implemented as dyn trait
/// object exposing a few options to registering sprites in Scene layers. This is a way less generic
/// solution than the one implemented currently, but it would allow [`Scene`] parametrization. For
/// example, if we would like to implement [`Scene<Context>`], where the [`Context`] is either a
/// WebGL or an OpenGL context (currently contexts are implemented as dyn traits instead).
/// ///
/// ## Scene Layers /// ## Scene Layers
/// Each display object instance contains an optional list of [`scene::LayerId`]. During object /// Each display object instance contains an optional list of [`scene::LayerId`]. During object
@ -630,13 +631,6 @@ pub struct Id(usize);
/// plays a very important role in decoupling the architecture. It allows objects and their children /// plays a very important role in decoupling the architecture. It allows objects and their children
/// to be assigned to a particular [`scene::Layer`], and thus allows for easy to use depth /// to be assigned to a particular [`scene::Layer`], and thus allows for easy to use depth
/// management. /// management.
///
/// ## Future Development
/// Please note, that currently, the design is abstract over [`Host`], but it is not abstract over
/// scene layers. This may change in the future, but first, the [`Scene`] implementation has to be
/// refactored to allow the creation of [`Symbol`]s without requirement of a [`Scene`] instance
/// existence. See this ticket to learn more: https://github.com/enso-org/ide/issues/1129 .
#[derive(Derivative)] #[derive(Derivative)]
#[derive(CloneRef)] #[derive(CloneRef)]
#[derivative(Clone(bound = ""))] #[derivative(Clone(bound = ""))]

View File

@ -10,6 +10,7 @@ pub use layer::Layer;
pub use crate::system::web::dom::Shape; pub use crate::system::web::dom::Shape;
use crate::prelude::*; use crate::prelude::*;
use web::traits::*;
use crate::animation; use crate::animation;
use crate::control::callback; use crate::control::callback;
@ -30,20 +31,20 @@ use crate::display::style::data::DataMatch;
use crate::display::symbol::registry::SymbolRegistry; use crate::display::symbol::registry::SymbolRegistry;
use crate::display::symbol::Symbol; use crate::display::symbol::Symbol;
use crate::display::symbol::SymbolId; use crate::display::symbol::SymbolId;
use crate::system;
use crate::system::gpu::data::attribute; use crate::system::gpu::data::attribute;
use crate::system::gpu::data::uniform::Uniform; use crate::system::gpu::data::uniform::Uniform;
use crate::system::gpu::data::uniform::UniformScope; use crate::system::gpu::data::uniform::UniformScope;
use crate::system::gpu::shader::Context;
use crate::system::web; use crate::system::web;
use crate::system::web::IgnoreContextMenuHandle; use crate::system::web::EventListenerHandle;
use crate::system::web::NodeInserter; use crate::system::Context;
use crate::system::web::StyleSetter; use crate::system::ContextLostHandler;
use enso_frp as frp; use enso_frp as frp;
use enso_frp::io::js::CurrentJsEvent; use enso_frp::io::js::CurrentJsEvent;
use enso_shapely::shared; use enso_shapely::shared;
use std::any::TypeId; use std::any::TypeId;
use web_sys::HtmlElement; use web::HtmlElement;
pub trait MouseTarget: Debug + 'static { pub trait MouseTarget: Debug + 'static {
@ -326,7 +327,7 @@ impl Mouse {
let position = variables.add_or_panic("mouse_position", Vector2::new(0, 0)); let position = variables.add_or_panic("mouse_position", Vector2::new(0, 0));
let hover_ids = variables.add_or_panic("mouse_hover_ids", target.to_internal(&logger)); let hover_ids = variables.add_or_panic("mouse_hover_ids", target.to_internal(&logger));
let target = Rc::new(Cell::new(target)); let target = Rc::new(Cell::new(target));
let mouse_manager = MouseManager::new_separated(&root.clone_ref().into(), &web::window()); let mouse_manager = MouseManager::new_separated(&root.clone_ref().into(), &web::window);
let frp = frp::io::Mouse::new(); let frp = frp::io::Mouse::new();
let on_move = mouse_manager.on_move.add(current_js_event.make_event_handler( let on_move = mouse_manager.on_move.add(current_js_event.make_event_handler(
f!([frp,scene_frp,position,last_position] (event:&mouse::OnMove) { f!([frp,scene_frp,position,last_position] (event:&mouse::OnMove) {
@ -414,10 +415,8 @@ pub struct Keyboard {
impl Keyboard { impl Keyboard {
pub fn new(current_event: &CurrentJsEvent) -> Self { pub fn new(current_event: &CurrentJsEvent) -> Self {
let logger = Logger::new("keyboard");
let frp = enso_frp::io::keyboard::Keyboard::default(); let frp = enso_frp::io::keyboard::Keyboard::default();
let bindings = let bindings = Rc::new(enso_frp::io::keyboard::DomBindings::new(&frp, current_event));
Rc::new(enso_frp::io::keyboard::DomBindings::new(&logger, &frp, current_event));
Self { frp, bindings } Self { frp, bindings }
} }
} }
@ -440,12 +439,12 @@ pub struct Dom {
impl Dom { impl Dom {
/// Constructor. /// Constructor.
pub fn new(logger: &Logger) -> Self { pub fn new(logger: &Logger) -> Self {
let root = web::create_div(); let root = web::document.create_div_or_panic();
let layers = DomLayers::new(logger, &root); let layers = DomLayers::new(logger, &root);
root.set_class_name("scene"); root.set_class_name("scene");
root.set_style_or_panic("height", "100vh"); root.set_style_or_warn("height", "100vh");
root.set_style_or_panic("width", "100vw"); root.set_style_or_warn("width", "100vw");
root.set_style_or_panic("display", "block"); root.set_style_or_warn("display", "block");
let root = web::dom::WithKnownShape::new(&root); let root = web::dom::WithKnownShape::new(&root);
Self { root, layers } Self { root, layers }
} }
@ -488,42 +487,42 @@ pub struct DomLayers {
/// Front DOM scene layer. /// Front DOM scene layer.
pub front: DomScene, pub front: DomScene,
/// The WebGL scene layer. /// The WebGL scene layer.
pub canvas: web_sys::HtmlCanvasElement, pub canvas: web::HtmlCanvasElement,
} }
impl DomLayers { impl DomLayers {
/// Constructor. /// Constructor.
pub fn new(logger: &Logger, dom: &web_sys::HtmlDivElement) -> Self { pub fn new(logger: &Logger, dom: &web::HtmlDivElement) -> Self {
let welcome_screen = DomScene::new(logger); let welcome_screen = DomScene::new(logger);
welcome_screen.dom.set_class_name("welcome_screen"); welcome_screen.dom.set_class_name("welcome_screen");
welcome_screen.dom.set_style_or_warn("z-index", "0", logger); welcome_screen.dom.set_style_or_warn("z-index", "0");
dom.append_or_panic(&welcome_screen.dom); dom.append_or_warn(&welcome_screen.dom);
let back = DomScene::new(logger); let back = DomScene::new(logger);
back.dom.set_class_name("back"); back.dom.set_class_name("back");
back.dom.set_style_or_warn("z-index", "1", logger); back.dom.set_style_or_warn("z-index", "1");
dom.append_or_panic(&back.dom); dom.append_or_warn(&back.dom);
let fullscreen_vis = DomScene::new(logger); let fullscreen_vis = DomScene::new(logger);
fullscreen_vis.dom.set_class_name("fullscreen_vis"); fullscreen_vis.dom.set_class_name("fullscreen_vis");
fullscreen_vis.dom.set_style_or_warn("z-index", "2", logger); fullscreen_vis.dom.set_style_or_warn("z-index", "2");
dom.append_or_panic(&fullscreen_vis.dom); dom.append_or_warn(&fullscreen_vis.dom);
let canvas = web::create_canvas(); let canvas = web::document.create_canvas_or_panic();
canvas.set_style_or_warn("display", "block", logger); canvas.set_style_or_warn("display", "block");
canvas.set_style_or_warn("z-index", "3", logger); canvas.set_style_or_warn("z-index", "3");
// These properties are set by `DomScene::new` constuctor for other layers. // These properties are set by `DomScene::new` constuctor for other layers.
// See its documentation for more info. // See its documentation for more info.
canvas.set_style_or_warn("position", "absolute", logger); canvas.set_style_or_warn("position", "absolute");
canvas.set_style_or_warn("height", "100vh", logger); canvas.set_style_or_warn("height", "100vh");
canvas.set_style_or_warn("width", "100vw", logger); canvas.set_style_or_warn("width", "100vw");
canvas.set_style_or_warn("pointer-events", "none", logger); canvas.set_style_or_warn("pointer-events", "none");
dom.append_or_panic(&canvas); dom.append_or_warn(&canvas);
let front = DomScene::new(logger); let front = DomScene::new(logger);
front.dom.set_class_name("front"); front.dom.set_class_name("front");
front.dom.set_style_or_warn("z-index", "4", logger); front.dom.set_style_or_warn("z-index", "4");
dom.append_or_panic(&front.dom); dom.append_or_warn(&front.dom);
Self { back, welcome_screen, fullscreen_vis, front, canvas } Self { back, welcome_screen, fullscreen_vis, front, canvas }
} }
@ -565,74 +564,107 @@ pub struct Dirty {
shape: ShapeDirty, shape: ShapeDirty,
} }
impl Dirty {
pub fn new<OnMut: Fn() + Clone + 'static>(logger: &Logger, on_mut: OnMut) -> Self {
let sub_logger = Logger::new_sub(logger, "shape_dirty");
let shape = ShapeDirty::new(sub_logger, Box::new(on_mut.clone()));
let sub_logger = Logger::new_sub(logger, "symbols_dirty");
let symbols = SymbolRegistryDirty::new(sub_logger, Box::new(on_mut));
Self { symbols, shape }
}
}
// ================ // ================
// === Renderer === // === Renderer ===
// ================ // ================
/// Scene renderer. Manages the initialization and lifetime of both [`render::Pipeline`] and the
/// [`render::Composer`].
///
/// Please note that the composer can be empty if the context was either not provided yet or it was
/// lost.
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct Renderer { pub struct Renderer {
pub logger: Logger, pub logger: Logger,
dom: Dom, dom: Dom,
context: Context, variables: UniformScope,
variables: UniformScope,
pub pipeline: Rc<CloneCell<render::Pipeline>>, pub pipeline: Rc<CloneCell<render::Pipeline>>,
pub composer: Rc<RefCell<render::Composer>>, pub composer: Rc<RefCell<Option<render::Composer>>>,
} }
impl Renderer { impl Renderer {
fn new(logger: impl AnyLogger, dom: &Dom, context: &Context, variables: &UniformScope) -> Self { fn new(logger: impl AnyLogger, dom: &Dom, variables: &UniformScope) -> Self {
let logger = Logger::new_sub(logger, "renderer"); let logger = Logger::new_sub(logger, "renderer");
let dom = dom.clone_ref(); let dom = dom.clone_ref();
let context = context.clone_ref();
let variables = variables.clone_ref(); let variables = variables.clone_ref();
let pipeline = default(); let pipeline = default();
let shape = dom.shape().device_pixels(); let composer = default();
let width = shape.width as i32; Self { logger, dom, variables, pipeline, composer }
let height = shape.height as i32; }
let composer = render::Composer::new(&pipeline, &context, &variables, width, height);
let pipeline = Rc::new(CloneCell::new(pipeline));
let composer = Rc::new(RefCell::new(composer));
context.enable(Context::BLEND); fn set_context(&self, context: Option<&Context>) {
// To learn more about the blending equations used here, please see the following articles: let composer = context.map(|context| {
// - http://www.realtimerendering.com/blog/gpus-prefer-premultiplication // To learn more about the blending equations used here, please see the following
// - https://www.khronos.org/opengl/wiki/Blending#Colors // articles:
context.blend_equation_separate(Context::FUNC_ADD, Context::FUNC_ADD); // - http://www.realtimerendering.com/blog/gpus-prefer-premultiplication
context.blend_func_separate( // - https://www.khronos.org/opengl/wiki/Blending#Colors
Context::ONE, context.enable(Context::BLEND);
Context::ONE_MINUS_SRC_ALPHA, context.blend_equation_separate(Context::FUNC_ADD, Context::FUNC_ADD);
Context::ONE, context.blend_func_separate(
Context::ONE_MINUS_SRC_ALPHA, Context::ONE,
); Context::ONE_MINUS_SRC_ALPHA,
Context::ONE,
Context::ONE_MINUS_SRC_ALPHA,
);
Self { logger, dom, context, variables, pipeline, composer } let (width, height) = self.view_size();
let pipeline = self.pipeline.get();
render::Composer::new(&pipeline, context, &self.variables, width, height)
});
*self.composer.borrow_mut() = composer;
self.update_composer_pipeline();
} }
/// Set the pipeline of this renderer. /// Set the pipeline of this renderer.
pub fn set_pipeline<P: Into<render::Pipeline>>(&self, pipeline: P) { pub fn set_pipeline(&self, pipeline: render::Pipeline) {
let pipeline = pipeline.into();
self.composer.borrow_mut().set_pipeline(&pipeline);
self.pipeline.set(pipeline); self.pipeline.set(pipeline);
self.update_composer_pipeline()
}
/// Reload the composer pipeline.
fn update_composer_pipeline(&self) {
if let Some(composer) = &mut *self.composer.borrow_mut() {
composer.set_pipeline(&self.pipeline.get());
}
} }
/// Reload the composer after scene shape change. /// Reload the composer after scene shape change.
fn resize_composer(&self) { fn resize_composer(&self) {
if let Some(composer) = &mut *self.composer.borrow_mut() {
let (width, height) = self.view_size();
composer.resize(width, height);
}
}
// The width and height in device pixels should be integers. If they are not then this is due to
// rounding errors. We round to the nearest integer to compensate for those errors.
fn view_size(&self) -> (i32, i32) {
let shape = self.dom.shape().device_pixels(); let shape = self.dom.shape().device_pixels();
// The width and height in device pixels should be integers. If they are not then this is
// due to rounding errors. We round to the nearest integer to compensate for those errors.
let width = shape.width.round() as i32; let width = shape.width.round() as i32;
let height = shape.height.round() as i32; let height = shape.height.round() as i32;
self.composer.borrow_mut().resize(width, height); (width, height)
} }
/// Run the renderer. /// Run the renderer.
pub fn run(&self) { pub fn run(&self) {
debug!(self.logger, "Running.", || { if let Some(composer) = &mut *self.composer.borrow_mut() {
self.composer.borrow_mut().run(); debug!(self.logger, "Running.", || {
}) composer.run();
})
}
} }
} }
@ -643,7 +675,7 @@ impl Renderer {
// ============== // ==============
/// Please note that currently the `Layers` structure is implemented in a hacky way. It assumes the /// Please note that currently the `Layers` structure is implemented in a hacky way. It assumes the
/// existence of several layers, which are needed for the GUI to display shapes properly. This \ /// existence of several layers, which are needed for the GUI to display shapes properly. This
/// should be abstracted away in the future. /// should be abstracted away in the future.
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug)]
pub struct HardcodedLayers { pub struct HardcodedLayers {
@ -801,33 +833,33 @@ impl Extensions {
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug)]
pub struct SceneData { pub struct SceneData {
pub display_object: display::object::Instance, pub display_object: display::object::Instance,
pub dom: Dom, pub dom: Dom,
pub context: Context, pub context: Rc<RefCell<Option<Context>>>,
pub symbols: SymbolRegistry, pub context_lost_handler: Rc<RefCell<Option<ContextLostHandler>>>,
pub variables: UniformScope, pub symbols: SymbolRegistry,
pub current_js_event: CurrentJsEvent, pub variables: UniformScope,
pub mouse: Mouse, pub current_js_event: CurrentJsEvent,
pub keyboard: Keyboard, pub mouse: Mouse,
pub uniforms: Uniforms, pub keyboard: Keyboard,
pub shapes: ShapeRegistry, pub uniforms: Uniforms,
pub stats: Stats, pub shapes: ShapeRegistry,
pub dirty: Dirty, pub stats: Stats,
pub logger: Logger, pub dirty: Dirty,
pub renderer: Renderer, pub logger: Logger,
pub layers: HardcodedLayers, pub renderer: Renderer,
pub style_sheet: style::Sheet, pub layers: HardcodedLayers,
pub bg_color_var: style::Var, pub style_sheet: style::Sheet,
pub bg_color_change: callback::Handle, pub bg_color_var: style::Var,
pub frp: Frp, pub bg_color_change: callback::Handle,
extensions: Extensions, pub frp: Frp,
disable_context_menu: Rc<IgnoreContextMenuHandle>, extensions: Extensions,
disable_context_menu: Rc<EventListenerHandle>,
} }
impl SceneData { impl SceneData {
/// Create new instance with the provided on-dirty callback. /// Create new instance with the provided on-dirty callback.
pub fn new<OnMut: Fn() + Clone + 'static>( pub fn new<OnMut: Fn() + Clone + 'static>(
parent_dom: &HtmlElement,
logger: Logger, logger: Logger,
stats: &Stats, stats: &Stats,
on_mut: OnMut, on_mut: OnMut,
@ -835,37 +867,24 @@ impl SceneData {
debug!(logger, "Initializing."); debug!(logger, "Initializing.");
let dom = Dom::new(&logger); let dom = Dom::new(&logger);
parent_dom.append_child(&dom.root).unwrap();
dom.recompute_shape_with_reflow();
let display_object = display::object::Instance::new(&logger); let display_object = display::object::Instance::new(&logger);
display_object.force_set_visibility(true); display_object.force_set_visibility(true);
let context = web::get_webgl2_context(&dom.layers.canvas);
let sub_logger = Logger::new_sub(&logger, "shape_dirty");
let shape_dirty = ShapeDirty::new(sub_logger, Box::new(on_mut.clone()));
let sub_logger = Logger::new_sub(&logger, "symbols_dirty");
let dirty_flag = SymbolRegistryDirty::new(sub_logger, Box::new(on_mut));
let on_change = enclose!((dirty_flag) move || dirty_flag.set());
let var_logger = Logger::new_sub(&logger, "global_variables"); let var_logger = Logger::new_sub(&logger, "global_variables");
let variables = UniformScope::new(var_logger); let variables = UniformScope::new(var_logger);
let symbols = SymbolRegistry::mk(&variables, stats, &logger, on_change); let dirty = Dirty::new(&logger, on_mut);
// FIXME: This should be abstracted away and should also handle context loss when Symbol let symbols_dirty = &dirty.symbols;
// definition will be finally refactored in such way, that it would not require let symbols = SymbolRegistry::mk(&variables, stats, &logger, f!(symbols_dirty.set()));
// Scene instance to be created.
symbols.set_context(Some(&context));
let symbols_dirty = dirty_flag;
let layers = HardcodedLayers::new(&logger); let layers = HardcodedLayers::new(&logger);
let stats = stats.clone(); let stats = stats.clone();
let shapes = ShapeRegistry::default(); let shapes = ShapeRegistry::default();
let uniforms = Uniforms::new(&variables); let uniforms = Uniforms::new(&variables);
let dirty = Dirty { symbols: symbols_dirty, shape: shape_dirty }; let renderer = Renderer::new(&logger, &dom, &variables);
let renderer = Renderer::new(&logger, &dom, &context, &variables);
let style_sheet = style::Sheet::new(); let style_sheet = style::Sheet::new();
let current_js_event = CurrentJsEvent::new(); let current_js_event = CurrentJsEvent::new();
let frp = Frp::new(&dom.root.shape); let frp = Frp::new(&dom.root.shape);
let mouse_logger = Logger::new_sub(&logger, "mouse"); let mouse_logger = Logger::new_sub(&logger, "mouse");
let mouse = Mouse::new(&frp, &dom.root, &variables, &current_js_event, mouse_logger); let mouse = Mouse::new(&frp, &dom.root, &variables, &current_js_event, mouse_logger);
let disable_context_menu = Rc::new(web::ignore_context_menu(&dom.root).unwrap()); let disable_context_menu = Rc::new(web::ignore_context_menu(&dom.root));
let keyboard = Keyboard::new(&current_js_event); let keyboard = Keyboard::new(&current_js_event);
let network = &frp.network; let network = &frp.network;
let extensions = Extensions::default(); let extensions = Extensions::default();
@ -873,7 +892,7 @@ impl SceneData {
let bg_color_change = bg_color_var.on_change(f!([dom](change){ let bg_color_change = bg_color_var.on_change(f!([dom](change){
change.color().for_each(|color| { change.color().for_each(|color| {
let color = color.to_javascript_string(); let color = color.to_javascript_string();
dom.root.set_style_or_panic("background-color",color); dom.root.set_style_or_warn("background-color",color);
}) })
})); }));
@ -883,10 +902,13 @@ impl SceneData {
} }
uniforms.pixel_ratio.set(dom.shape().pixel_ratio); uniforms.pixel_ratio.set(dom.shape().pixel_ratio);
let context = default();
let context_lost_handler = default();
Self { Self {
display_object, display_object,
dom, dom,
context, context,
context_lost_handler,
symbols, symbols,
variables, variables,
current_js_event, current_js_event,
@ -908,6 +930,13 @@ impl SceneData {
} }
} }
pub fn set_context(&self, context: Option<&Context>) {
self.symbols.set_context(context);
*self.context.borrow_mut() = context.cloned();
self.dirty.shape.set();
self.renderer.set_context(context);
}
pub fn shape(&self) -> &frp::Sampler<Shape> { pub fn shape(&self) -> &frp::Sampler<Shape> {
&self.dom.root.shape &self.dom.root.shape
} }
@ -990,12 +1019,18 @@ impl SceneData {
let width = canvas.width.round() as i32; let width = canvas.width.round() as i32;
let height = canvas.height.round() as i32; let height = canvas.height.round() as i32;
debug!(self.logger, "Resized to {screen.width}px x {screen.height}px.", || { debug!(self.logger, "Resized to {screen.width}px x {screen.height}px.", || {
self.dom.layers.canvas.set_attribute("width", &width.to_string()).unwrap(); self.dom.layers.canvas.set_attribute_or_warn("width", &width.to_string());
self.dom.layers.canvas.set_attribute("height", &height.to_string()).unwrap(); self.dom.layers.canvas.set_attribute_or_warn("height", &height.to_string());
self.context.viewport(0, 0, width, height); if let Some(context) = &*self.context.borrow() {
context.viewport(0, 0, width, height);
}
}); });
} }
pub fn render(&self) {
self.renderer.run()
}
pub fn screen_to_scene_coordinates(&self, position: Vector3<f32>) -> Vector3<f32> { pub fn screen_to_scene_coordinates(&self, position: Vector3<f32>) -> Vector3<f32> {
let position = position / self.camera().zoom(); let position = position / self.camera().zoom();
let position = Vector4::new(position.x, position.y, position.z, 1.0); let position = Vector4::new(position.x, position.y, position.z, 1.0);
@ -1043,26 +1078,54 @@ pub struct Scene {
impl Scene { impl Scene {
pub fn new<OnMut: Fn() + Clone + 'static>( pub fn new<OnMut: Fn() + Clone + 'static>(
parent_dom: &HtmlElement,
logger: impl AnyLogger, logger: impl AnyLogger,
stats: &Stats, stats: &Stats,
on_mut: OnMut, on_mut: OnMut,
) -> Self { ) -> Self {
let logger = Logger::new_sub(logger, "scene"); let logger = Logger::new_sub(logger, "scene");
let no_mut_access = SceneData::new(parent_dom, logger, stats, on_mut); let no_mut_access = SceneData::new(logger, stats, on_mut);
let this = Self { no_mut_access }; let this = Self { no_mut_access };
// FIXME MEMORY LEAK in all lines below: // FIXME MEMORY LEAK:
this.no_mut_access.shapes.rc.borrow_mut().scene = Some(this.clone_ref()); this.no_mut_access.shapes.rc.borrow_mut().scene = Some(this.clone_ref());
this this
} }
pub fn display_in(&self, parent_dom: impl DomPath) {
match parent_dom.try_into_dom_element() {
None => error!(&self.logger, "The scene host element could not be found."),
Some(parent_dom) => {
parent_dom.append_or_warn(&self.dom.root);
self.dom.recompute_shape_with_reflow();
self.uniforms.pixel_ratio.set(self.dom.shape().pixel_ratio);
self.init();
}
}
}
fn init(&self) {
let context_loss_handler = crate::system::context::init_webgl_2_context(self);
match context_loss_handler {
Err(err) => error!(self.logger, "{err}"),
Ok(handler) => *self.context_lost_handler.borrow_mut() = Some(handler),
}
}
pub fn extension<T: Extension>(&self) -> T { pub fn extension<T: Extension>(&self) -> T {
self.extensions.get(self) self.extensions.get(self)
} }
} }
impl system::context::Display for Scene {
fn device_context_handler(&self) -> &system::context::DeviceContextHandler {
&self.dom.layers.canvas
}
fn set_context(&self, context: Option<&Context>) {
self.no_mut_access.set_context(context)
}
}
impl AsRef<SceneData> for Scene { impl AsRef<SceneData> for Scene {
fn as_ref(&self) -> &SceneData { fn as_ref(&self) -> &SceneData {
&self.no_mut_access &self.no_mut_access
@ -1084,17 +1147,19 @@ impl Deref for Scene {
impl Scene { impl Scene {
pub fn update(&self, t: animation::TimeInfo) { pub fn update(&self, t: animation::TimeInfo) {
debug!(self.logger, "Updating.", || { if self.context.borrow().is_some() {
self.frp.frame_time_source.emit(t.local); debug!(self.logger, "Updating.", || {
// Please note that `update_camera` is called first as it may trigger FRP events which self.frp.frame_time_source.emit(t.local);
// may change display objects layout. // Please note that `update_camera` is called first as it may trigger FRP events
self.update_camera(self); // which may change display objects layout.
self.display_object.update(self); self.update_camera(self);
self.layers.update(); self.display_object.update(self);
self.update_shape(); self.layers.update();
self.update_symbols(); self.update_shape();
self.handle_mouse_events(); self.update_symbols();
}) self.handle_mouse_events();
})
}
} }
} }
@ -1109,3 +1174,46 @@ impl display::Object for Scene {
&self.display_object &self.display_object
} }
} }
// ===============
// === DomPath ===
// ===============
/// Abstraction for DOM path. It can be either a specific HTML element or a DOM id, a string used
/// to query the DOM structure to find the corresponding HTML node.
#[allow(missing_docs)]
pub trait DomPath {
fn try_into_dom_element(self) -> Option<HtmlElement>;
}
impl DomPath for HtmlElement {
fn try_into_dom_element(self) -> Option<HtmlElement> {
Some(self)
}
}
impl<'t> DomPath for &'t HtmlElement {
fn try_into_dom_element(self) -> Option<HtmlElement> {
Some(self.clone())
}
}
impl DomPath for String {
fn try_into_dom_element(self) -> Option<HtmlElement> {
web::document.get_html_element_by_id(&self)
}
}
impl<'t> DomPath for &'t String {
fn try_into_dom_element(self) -> Option<HtmlElement> {
web::document.get_html_element_by_id(self)
}
}
impl<'t> DomPath for &'t str {
fn try_into_dom_element(self) -> Option<HtmlElement> {
web::document.get_html_element_by_id(self)
}
}

View File

@ -1,6 +1,7 @@
//! This module defines a DOM management utilities. //! This module defines a DOM management utilities.
use crate::prelude::*; use crate::prelude::*;
use web::traits::*;
use crate::display::camera::camera2d::Projection; use crate::display::camera::camera2d::Projection;
use crate::display::camera::Camera2d; use crate::display::camera::Camera2d;
@ -8,13 +9,13 @@ use crate::display::object::traits::*;
use crate::display::symbol::dom::eps; use crate::display::symbol::dom::eps;
use crate::display::symbol::dom::inverse_y_translation; use crate::display::symbol::dom::inverse_y_translation;
use crate::display::symbol::DomSymbol; use crate::display::symbol::DomSymbol;
use crate::system::gpu::data::JsBufferView;
use crate::system::web; use crate::system::web;
use crate::system::web::NodeInserter; use web::HtmlDivElement;
use crate::system::web::StyleSetter;
#[cfg(target_arch = "wasm32")]
use crate::system::gpu::data::JsBufferView;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::wasm_bindgen;
use web_sys::HtmlDivElement;
@ -22,6 +23,7 @@ use web_sys::HtmlDivElement;
// === Js Bindings === // === Js Bindings ===
// =================== // ===================
#[cfg(target_arch = "wasm32")]
mod js { mod js {
use super::*; use super::*;
#[wasm_bindgen(inline_js = " #[wasm_bindgen(inline_js = "
@ -64,6 +66,8 @@ mod js {
} }
} }
#[cfg(target_arch = "wasm32")]
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn setup_camera_perspective(dom: &web::JsValue, near: f32, matrix: &Matrix4<f32>) { fn setup_camera_perspective(dom: &web::JsValue, near: f32, matrix: &Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't // Views to WASM memory are only valid as long the backing buffer isn't
@ -75,6 +79,7 @@ fn setup_camera_perspective(dom: &web::JsValue, near: f32, matrix: &Matrix4<f32>
} }
} }
#[cfg(target_arch = "wasm32")]
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn setup_camera_orthographic(dom: &web::JsValue, matrix: &Matrix4<f32>) { fn setup_camera_orthographic(dom: &web::JsValue, matrix: &Matrix4<f32>) {
// Views to WASM memory are only valid as long the backing buffer isn't // Views to WASM memory are only valid as long the backing buffer isn't
@ -86,6 +91,18 @@ fn setup_camera_orthographic(dom: &web::JsValue, matrix: &Matrix4<f32>) {
} }
} }
#[cfg(not(target_arch = "wasm32"))]
mod js {
use super::*;
pub fn setup_perspective(_dom: &web::JsValue, _znear: &web::JsValue) {}
}
#[cfg(not(target_arch = "wasm32"))]
fn setup_camera_perspective(_dom: &web::JsValue, _near: f32, _matrix: &Matrix4<f32>) {}
#[cfg(not(target_arch = "wasm32"))]
fn setup_camera_orthographic(_dom: &web::JsValue, _matrix: &Matrix4<f32>) {}
// ============= // =============
@ -149,27 +166,27 @@ impl DomScene {
/// Constructor. /// Constructor.
pub fn new(logger: impl AnyLogger) -> Self { pub fn new(logger: impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger, "DomScene"); let logger = Logger::new_sub(logger, "DomScene");
let dom = web::create_div(); let dom = web::document.create_div_or_panic();
let view_projection_dom = web::create_div(); let view_projection_dom = web::document.create_div_or_panic();
dom.set_class_name("dom-scene-layer"); dom.set_class_name("dom-scene-layer");
// z-index works on positioned elements only. // z-index works on positioned elements only.
dom.set_style_or_warn("position", "absolute", &logger); dom.set_style_or_warn("position", "absolute");
dom.set_style_or_warn("top", "0px", &logger); dom.set_style_or_warn("top", "0px");
dom.set_style_or_warn("overflow", "hidden", &logger); dom.set_style_or_warn("overflow", "hidden");
dom.set_style_or_warn("overflow", "hidden", &logger); dom.set_style_or_warn("overflow", "hidden");
dom.set_style_or_warn("width", "100%", &logger); dom.set_style_or_warn("width", "100%");
dom.set_style_or_warn("height", "100%", &logger); dom.set_style_or_warn("height", "100%");
// We ignore pointer events to avoid stealing them from other DomScenes. // We ignore pointer events to avoid stealing them from other DomScenes.
// See https://github.com/enso-org/enso/blob/develop/lib/rust/ensogl/doc/mouse-handling.md. // See https://github.com/enso-org/enso/blob/develop/lib/rust/ensogl/doc/mouse-handling.md.
dom.set_style_or_warn("pointer-events", "none", &logger); dom.set_style_or_warn("pointer-events", "none");
view_projection_dom.set_class_name("view_projection"); view_projection_dom.set_class_name("view_projection");
view_projection_dom.set_style_or_warn("width", "100%", &logger); view_projection_dom.set_style_or_warn("width", "100%");
view_projection_dom.set_style_or_warn("height", "100%", &logger); view_projection_dom.set_style_or_warn("height", "100%");
view_projection_dom.set_style_or_warn("transform-style", "preserve-3d", &logger); view_projection_dom.set_style_or_warn("transform-style", "preserve-3d");
dom.append_or_warn(&view_projection_dom, &logger); dom.append_or_warn(&view_projection_dom);
let data = DomSceneData::new(dom, view_projection_dom, logger); let data = DomSceneData::new(dom, view_projection_dom, logger);
let data = Rc::new(data); let data = Rc::new(data);
@ -183,13 +200,13 @@ impl DomScene {
/// Sets the z-index of this DOM element. /// Sets the z-index of this DOM element.
pub fn set_z_index(&self, z: i32) { pub fn set_z_index(&self, z: i32) {
self.data.dom.set_style_or_warn("z-index", z.to_string(), &self.logger); self.data.dom.set_style_or_warn("z-index", z.to_string());
} }
/// Sets the CSS property `filter: grayscale({value})` on this element. A value of 0.0 displays /// Sets the CSS property `filter: grayscale({value})` on this element. A value of 0.0 displays
/// the element normally. A value of 1.0 will make the element completely gray. /// the element normally. A value of 1.0 will make the element completely gray.
pub fn filter_grayscale(&self, value: f32) { pub fn filter_grayscale(&self, value: f32) {
self.data.dom.set_style_or_warn("filter", format!("grayscale({})", value), &self.logger); self.data.dom.set_style_or_warn("filter", format!("grayscale({})", value));
} }
/// Creates a new instance of DomSymbol and adds it to parent. /// Creates a new instance of DomSymbol and adds it to parent.
@ -197,11 +214,11 @@ impl DomScene {
let dom = object.dom(); let dom = object.dom();
let data = &self.data; let data = &self.data;
if object.is_visible() { if object.is_visible() {
self.view_projection_dom.append_or_panic(dom); self.view_projection_dom.append_or_warn(dom);
} }
object.display_object().set_on_hide(f_!(dom.remove())); object.display_object().set_on_hide(f_!(dom.remove()));
object.display_object().set_on_show(f__!([data,dom] { object.display_object().set_on_show(f__!([data,dom] {
data.view_projection_dom.append_or_panic(&dom) data.view_projection_dom.append_or_warn(&dom)
})); }));
} }

View File

@ -4,14 +4,33 @@
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "/src/display/shape/primitive/glsl/overload.js")] mod js {
extern "C" { use super::*;
/// Returns GLSL code which redirects mangled function names to their original primitive #[wasm_bindgen(module = "/src/display/shape/primitive/glsl/overload.js")]
/// definitions. extern "C" {
#[allow(unsafe_code)] /// Returns GLSL code which redirects mangled function names to their original primitive
pub fn builtin_redirections() -> String; /// definitions.
#[allow(unsafe_code)]
pub fn builtin_redirections() -> String;
/// Mangles the provided GLSL code to allow primitive definitions overloading. /// Mangles the provided GLSL code to allow primitive definitions overloading.
#[allow(unsafe_code)] #[allow(unsafe_code)]
pub fn allow_overloading(s: &str) -> String; pub fn allow_overloading(s: &str) -> String;
}
} }
#[allow(missing_docs)]
mod mock {
pub fn builtin_redirections() -> String {
"".into()
}
pub fn allow_overloading(_: &str) -> String {
"".into()
}
}
#[cfg(target_arch = "wasm32")]
pub use js::*;
#[cfg(not(target_arch = "wasm32"))]
pub use mock::*;

Some files were not shown because too many files have changed in this diff Show More