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"
version = "0.1.0"
dependencies = [
"enso-web",
"ensogl-core",
"wasm-bindgen",
]

View File

@ -8,13 +8,9 @@ members = [
"build/rust-scripts",
"lib/rust/*",
"lib/rust/profiler/data",
"lib/rust/not-used/*",
"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.
default-members = ["app/gui", "lib/rust/*"]

View File

@ -6,4 +6,4 @@ edition = "2021"
[dependencies]
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"] }
shrinkwraprs = { version = "0.2.1" }
uuid = { version = "0.8", features = ["serde", "v5", "wasm-bindgen"] }
wasm-bindgen = { version = "0.2.58" }
wasm-bindgen = { version = "0.2.78" }
[dev-dependencies]
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-prelude = { path = "../../../../../lib/rust/prelude"}
enso-logger = { path = "../../../../../lib/rust/logger"}
wasm-bindgen = { version = "0.2.58", features = [
wasm-bindgen = { version = "0.2.78", features = [
"nightly",
"serde-serialize"
] }

View File

@ -13,6 +13,9 @@ use futures::task::LocalFutureObj;
use futures::task::LocalSpawn;
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
/// `animation::DynamicLoop` to do as much progress as possible on every animation frame.
#[derive(Debug)]
@ -22,7 +25,7 @@ pub struct EventLoopExecutor {
/// Executor's spawner handle.
pub spawner: LocalSpawner,
/// 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.
/// Also owns a shared handle to the `executor`.
cb_handle: Option<callback::Handle>,
@ -44,14 +47,14 @@ impl EventLoopExecutor {
///loop will live as long as this executor.
pub fn new_running() -> EventLoopExecutor {
let mut executor = EventLoopExecutor::new();
executor.start_running(animation::DynamicLoop::new());
executor.start_running();
executor
}
/// Returns a callback compatible with `animation::DynamicLoop` that once called shall
/// attempt achieving as much progress on this executor's tasks as possible
/// without stalling.
pub fn runner(&self) -> impl animation::DynamicLoopCallback {
pub fn runner(&self) -> impl FnMut(animation::TimeInfo) {
let executor = self.executor.clone();
move |_| {
// 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
/// required to keep it alive.
pub fn start_running(&mut self, event_loop: animation::DynamicLoop) {
let cb = self.runner();
self.cb_handle = Some(event_loop.on_frame(cb));
self.event_loop = Some(event_loop);
fn start_running(&mut self) {
self.event_loop = Some(MainLoop::new(Box::new(self.runner())));
}
/// 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 {
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 keyboard = &scene.keyboard.frp;

View File

@ -63,9 +63,9 @@ impl Initializer {
let executor = setup_global_executor();
executor::global::spawn(async move {
let ide = self.start().await;
web::get_element_by_id("loader")
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()))
.ok();
web::document
.get_element_by_id("loader")
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()));
std::mem::forget(ide);
});
std::mem::forget(executor);
@ -75,8 +75,7 @@ impl Initializer {
pub async fn start(self) -> Result<Ide, FailedIde> {
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_element);
let ensogl_app = ensogl::application::Application::new("root");
Initializer::register_views(&ensogl_app);
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 ide_view as view;
use ensogl::system::web;
use wasm_bindgen::prelude::*;
// 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 enso_prelude::*;
pub use ensogl::prelude::*;
pub use wasm_bindgen::prelude::*;
pub use crate::constants;
pub use crate::controller;
@ -116,8 +114,6 @@ pub mod prelude {
#[wasm_bindgen]
#[allow(dead_code)]
pub fn entry_point_ide() {
web::forward_panic_hook_to_error();
ensogl_text_msdf_sys::run_once_initialized(|| {
// Logging of build information.
#[cfg(debug_assertions)]

View File

@ -15,6 +15,7 @@ use crate::executor::global::spawn_stream_handler;
use crate::presenter::graph::state::State;
use enso_frp as frp;
use enso_web::traits::*;
use futures::future::LocalBoxFuture;
use ide_view as 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;
// ===============
// === Aliases ===
// ===============
@ -609,6 +611,30 @@ impl Graph {
impl controller::upload::DataProvider for ensogl_drop_manager::File {
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 enso_web::event::listener::Slot;
use enso_web::js_to_string;
use enso_web::traits::*;
use failure::Error;
use futures::channel::mpsc;
use json_rpc::Transport;
@ -34,8 +34,8 @@ pub enum ConnectingError {
impl ConnectingError {
/// Create a `ConstructionError` value from a JS value describing an error.
pub fn construction_error(js_val: impl AsRef<JsValue>) -> Self {
let text = js_to_string(js_val);
pub fn construction_error(js_val: impl AsRef<enso_web::JsValue>) -> Self {
let text = js_val.as_ref().print_to_string();
ConnectingError::ConstructionError(text)
}
}
@ -54,8 +54,8 @@ pub enum SendingError {
impl SendingError {
/// Constructs from the error yielded by one of the JS's WebSocket sending functions.
pub fn from_send_error(error: JsValue) -> SendingError {
SendingError::FailedToSend(js_to_string(&error))
pub fn from_send_error(error: wasm_bindgen::JsValue) -> SendingError {
SendingError::FailedToSend(error.print_to_string())
}
}
@ -192,7 +192,7 @@ impl Model {
}
/// 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.
self.auto_reconnect = false;
let normal_closure = 1000;
@ -229,7 +229,7 @@ impl Model {
/// Establish a new WS connection, using the same URL as the previous one.
/// 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 {
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.") {
error!(
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 |_| {
if let Some(model) = model.upgrade() {
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.
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
// appearing in the log. We explicitly check for this to get failure as
// early as possible.
@ -433,7 +433,7 @@ impl Transport for WebSocket {
let event = TransportEvent::BinaryMessage(binary_data);
channel::emit(&transmitter_copy, event);
} 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 = { version = "1.0", features = ["derive"] }
uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] }
wasm-bindgen = { version = "0.2.58", features = [
wasm-bindgen = { version = "0.2.78", features = [
"nightly",
"serde-serialize"
] }

View File

@ -17,4 +17,4 @@ ide-view = { path = "../.." }
parser = { path = "../../../language/parser" }
span-tree = { path = "../../../language/span-tree" }
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]
#[allow(dead_code)]
pub fn entry_point_interface() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
run_once_initialized(|| {
let app = Application::new(&web::get_html_element_by_id("root").unwrap());
let app = Application::new("root");
init(&app);
mem::forget(app);
});
@ -100,10 +98,10 @@ impl DummyTypeGenerator {
// ========================
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 scene = world.scene();
let scene = &world.default_scene;
let camera = scene.camera();
let navigator = Navigator::new(scene, &camera);
@ -252,7 +250,9 @@ fn init(app: &Application) {
let mut to_theme_switch = 100;
world
.on_frame(move |_| {
.on
.before_frame
.add(move |_| {
let _keep_alive = &navigator;
let _keep_alive = &root_view;
@ -292,9 +292,9 @@ fn init(app: &Application) {
// Temporary code removing the web-loader instance.
// To be changed in the future.
if was_rendered && !loader_hidden {
web::get_element_by_id("loader")
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()))
.ok();
web::document
.get_element_by_id("loader")
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()));
loader_hidden = true;
}
was_rendered = true;

View File

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

View File

@ -33,7 +33,7 @@ serde_json = { version = "1.0" }
serde = { version = "1.0", features = ["derive"] }
sourcemap = "6.0"
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]
version = "0.3.4"

View File

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

View File

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

View File

@ -97,7 +97,7 @@ impl AddNodeButton {
pub fn new(app: &Application) -> Self {
let view = ensogl_component::button::View::new(app);
let network = frp::Network::new("AddNodeButton");
let scene = app.display.scene();
let scene = &app.display.default_scene;
let camera = scene.camera();
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
/// covered by the background and is intended to make room for windows control buttons.
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 logger = Logger::new("Breadcrumbs");
let display_object = display::object::Instance::new(&logger);
@ -450,7 +450,7 @@ pub struct Breadcrumbs {
impl Breadcrumbs {
/// Constructor.
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 model = Rc::new(BreadcrumbsModel::new(app, &frp));
let network = &frp.network;

View File

@ -279,7 +279,7 @@ impl BreadcrumbModel {
method_pointer: &MethodPointer,
expression_id: &ast::Id,
) -> Self {
let scene = app.display.scene();
let scene = &app.display.default_scene;
let logger = Logger::new("Breadcrumbs");
let display_object = display::object::Instance::new(&logger);
let view_logger = Logger::new_sub(&logger, "view_logger");
@ -492,7 +492,7 @@ impl Breadcrumb {
let frp = Frp::new();
let model = Rc::new(BreadcrumbModel::new(app, &frp, method_pointer, expression_id));
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
// system (#795)

View File

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

View File

@ -1161,7 +1161,7 @@ impl Edge {
/// Constructor.
pub fn new(app: &Application) -> Self {
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 });
Self { model, network }.init(app)
}
@ -1182,7 +1182,7 @@ impl Edge {
let shape_events = &self.frp.shape_events;
let edge_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.back.register_proxy_frp(network, &input.shape_events);

View File

@ -426,7 +426,7 @@ impl NodeModel {
/// Constructor.
pub fn new(app: &Application, registry: visualization::Registry) -> Self {
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
// transitive. But removing them causes a visual glitches described in
// 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 main_logger = Logger::new_sub(&logger, "main_area");
@ -495,7 +495,7 @@ impl NodeModel {
let output = output::Area::new(&logger, app);
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);
display_object.add_child(&comment);
@ -624,7 +624,7 @@ impl Node {
// in https://github.com/enso-org/ide/issues/1031
// let comment_color = 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 action_bar = &model.action_bar.frp;
// 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 {
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 display_object = display::object::Instance::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::Scene;
use ensogl::system::web;
use ensogl::system::web::StyleSetter;
use ensogl::system::web::traits::*;
use ensogl_component::shadow;
use serde::Deserialize;
use serde::Serialize;
@ -93,7 +93,7 @@ impl Container {
let scene = scene.clone_ref();
let logger = Logger::new("error::Container");
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);
display_object.add_child(&background_dom);
@ -102,7 +102,7 @@ impl Container {
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
// system (#795)
let styles = StyleWatch::new(&scene.style_sheet);
@ -113,21 +113,21 @@ impl Container {
let bg_blue = bg_color.blue * 255.0;
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 (width, height) = SIZE;
let width = format!("{}.px", width);
let height = format!("{}.px", height);
let z_index = Z_INDEX.to_string();
let border_radius = format!("{}.px", BORDER_RADIUS);
background_dom.dom().set_style_or_warn("width", width, logger);
background_dom.dom().set_style_or_warn("height", height, logger);
background_dom.dom().set_style_or_warn("z-index", z_index, logger);
background_dom.dom().set_style_or_warn("overflow-y", "auto", logger);
background_dom.dom().set_style_or_warn("overflow-x", "auto", logger);
background_dom.dom().set_style_or_warn("background", bg_hex, logger);
background_dom.dom().set_style_or_warn("border-radius", border_radius, logger);
shadow::add_to_dom_element(&background_dom, &styles, logger);
background_dom.dom().set_style_or_warn("width", width);
background_dom.dom().set_style_or_warn("height", height);
background_dom.dom().set_style_or_warn("z-index", z_index);
background_dom.dom().set_style_or_warn("overflow-y", "auto");
background_dom.dom().set_style_or_warn("overflow-x", "auto");
background_dom.dom().set_style_or_warn("background", bg_hex);
background_dom.dom().set_style_or_warn("border-radius", border_radius);
shadow::add_to_dom_element(&background_dom, &styles);
background_dom
}

View File

@ -245,8 +245,8 @@ impl Model {
let label = app.new_view::<text::Area>();
let id_crumbs_map = default();
let expression = default();
let styles = StyleWatch::new(&app.display.scene().style_sheet);
let styles_frp = StyleWatchFrp::new(&app.display.scene().style_sheet);
let styles = StyleWatch::new(&app.display.default_scene.style_sheet);
let styles_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
display_object.add_child(&label);
display_object.add_child(&ports);
ports.add_child(&header);
@ -268,7 +268,7 @@ impl Model {
fn init(self) -> Self {
// 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.
let scene = self.app.display.scene();
let scene = &self.app.display.default_scene;
self.label.remove_from_scene_layer(&scene.layers.main);
self.label.add_to_scene_layer(&scene.layers.label);
@ -289,7 +289,7 @@ impl Model {
}
fn scene(&self) -> &Scene {
self.app.display.scene()
&self.app.display.default_scene
}
/// 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
// 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_frp = &self.model.styles_frp;
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 expression = default();
let port_count = default();
let styles = StyleWatch::new(&app.display.scene().style_sheet);
let styles_frp = StyleWatchFrp::new(&app.display.scene().style_sheet);
let styles = StyleWatch::new(&app.display.default_scene.style_sheet);
let styles_frp = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let frp = frp.output.clone_ref();
display_object.add_child(&label);
display_object.add_child(&ports);
@ -199,7 +199,7 @@ impl Model {
fn init(self) -> Self {
// 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.
let scene = self.app.display.scene();
let scene = &self.app.display.default_scene;
self.label.remove_from_scene_layer(&scene.layers.main);
self.label.add_to_scene_layer(&scene.layers.label);

View File

@ -196,7 +196,7 @@ impl Deref for ProfilingLabel {
impl ProfilingLabel {
/// Constructs a `ProfilingLabel` for the given application.
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 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
// 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.source.status <+ frp.input.set_status;

View File

@ -143,7 +143,7 @@ impl Deref for Button {
impl Button {
/// Constructs a new button for toggling the editor's view mode.
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 frp = Frp::new();
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
// 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(
ensogl_hardcoded_theme::application::tooltip::hide_delay_duration_ms,
0.0,

View File

@ -31,7 +31,7 @@ use ensogl::display::DomSymbol;
use ensogl::Animation;
use ensogl::system::web;
use ensogl::system::web::StyleSetter;
use ensogl::system::web::traits::*;
use ensogl_component::shadow;
@ -192,19 +192,19 @@ impl View {
bg_color.alpha
);
let div = web::create_div();
let div = web::document.create_div_or_panic();
let background_dom = DomSymbol::new(&div);
// TODO : We added a HTML background to the `View`, because "shape" background was
// overlapping the JS visualization. This should be further investigated
// while fixing rust visualization displaying. (#796)
background_dom.dom().set_style_or_warn("width", "0", &logger);
background_dom.dom().set_style_or_warn("height", "0", &logger);
background_dom.dom().set_style_or_warn("z-index", "1", &logger);
background_dom.dom().set_style_or_warn("overflow-y", "auto", &logger);
background_dom.dom().set_style_or_warn("overflow-x", "auto", &logger);
background_dom.dom().set_style_or_warn("background", bg_hex, &logger);
background_dom.dom().set_style_or_warn("border-radius", "14px", &logger);
shadow::add_to_dom_element(&background_dom, &styles, &logger);
// overlapping the JS visualization. This should be further investigated
// while fixing rust visualization displaying. (#796)
background_dom.dom().set_style_or_warn("width", "0");
background_dom.dom().set_style_or_warn("height", "0");
background_dom.dom().set_style_or_warn("z-index", "1");
background_dom.dom().set_style_or_warn("overflow-y", "auto");
background_dom.dom().set_style_or_warn("overflow-x", "auto");
background_dom.dom().set_style_or_warn("background", bg_hex);
background_dom.dom().set_style_or_warn("border-radius", "14px");
shadow::add_to_dom_element(&background_dom, &styles);
display_object.add_child(&background_dom);
Self { display_object, background, overlay, background_dom, scene }.init()
@ -259,7 +259,7 @@ pub struct ContainerModel {
impl ContainerModel {
/// Constructor.
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 display_object = 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.view.background.shape.sprite.size.set(zero());
self.view.overlay.size.set(zero());
dom.set_style_or_warn("width", "0", &self.logger);
dom.set_style_or_warn("height", "0", &self.logger);
bg_dom.set_style_or_warn("width", format!("{}px", size[0]), &self.logger);
bg_dom.set_style_or_warn("height", format!("{}px", size[1]), &self.logger);
dom.set_style_or_warn("width", "0");
dom.set_style_or_warn("height", "0");
bg_dom.set_style_or_warn("width", format!("{}px", size[0]));
bg_dom.set_style_or_warn("height", format!("{}px", size[1]));
self.action_bar.frp.set_size.emit(Vector2::zero());
} else {
// self.view.background.shape.radius.set(CORNER_RADIUS);
@ -402,10 +402,10 @@ impl ContainerModel {
self.view.background.radius.set(CORNER_RADIUS);
self.view.overlay.size.set(size);
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("height", format!("{}px", size[1]), &self.logger);
bg_dom.set_style_or_warn("width", "0", &self.logger);
bg_dom.set_style_or_warn("height", "0", &self.logger);
dom.set_style_or_warn("width", format!("{}px", size[0]));
dom.set_style_or_warn("height", format!("{}px", size[1]));
bg_dom.set_style_or_warn("width", "0");
bg_dom.set_style_or_warn("height", "0");
// self.fullscreen_view.background.shape.sprite.size.set(zero());
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 shapes = compound::events::MouseEvents::default();
app.display.scene().layers.below_main.add_exclusive(&hover_area);
app.display.scene().layers.below_main.add_exclusive(&background);
app.display.scene().layers.above_nodes.add_exclusive(&icons);
app.display.default_scene.layers.below_main.add_exclusive(&hover_area);
app.display.default_scene.layers.below_main.add_exclusive(&background);
app.display.default_scene.layers.above_nodes.add_exclusive(&icons);
shapes.add_sub_shape(&hover_area);
shapes.add_sub_shape(&background);
@ -359,7 +359,7 @@ impl ActionBar {
let network = &self.frp.network;
let frp = &self.frp;
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;
frp::extend! { network

View File

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

View File

@ -60,7 +60,7 @@ struct Model {
impl Model {
pub fn new(app: &Application, registry: visualization::Registry) -> Self {
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 }
}

View File

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

View File

@ -2,11 +2,7 @@
//!
//! For details of the API please see:
//! * docstrings in the `visualization.js` file;
//! * [visualization documentation](https://dev.enso.org/docs/ide/product/visualizations.html).
// FIXME: Can we simplify the above definition so its more minimal, yet functional?
mod function;
use function::Function;
//! * [visualization documentation](https://enso.org/docs/developer/ide/product/visualizations.html).
use crate::prelude::*;
@ -18,12 +14,13 @@ use crate::component::visualization::InstantiationResult;
use crate::visualization::foreign::java_script::Sources;
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 fmt::Formatter;
use js_sys;
use js_sys::JsString;
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> {
let source = sources.to_string(&project);
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)?;
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 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 = visualization::data::Format::from_str(&input_format).unwrap_or_default();
let label = label(&class)?;
let path = visualization::Path::new(project, label);
let signature = visualization::Signature::new(path, input_type, input_format);
@ -102,7 +96,7 @@ impl From<Definition> for visualization::Definition {
// === Utils ===
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>()?;
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::instance::PreprocessorConfiguration;
use crate::component::visualization::java_script;
use crate::component::visualization::java_script::binding::JsConsArgs;
use crate::component::visualization::java_script::method;
use crate::component::visualization::*;
@ -24,9 +25,8 @@ use ensogl::display::DomScene;
use ensogl::display::DomSymbol;
use ensogl::display::Scene;
use ensogl::system::web;
use ensogl::system::web::traits::*;
use ensogl::system::web::JsValue;
use ensogl::system::web::StyleSetter;
use js_sys;
use std::fmt::Formatter;
@ -89,8 +89,8 @@ type PreprocessorCallbackCell = Rc<RefCell<Option<Box<dyn PreprocessorCallback>>
pub struct InstanceModel {
pub root_node: DomSymbol,
pub logger: Logger,
on_data_received: Rc<Option<js_sys::Function>>,
set_size: Rc<Option<js_sys::Function>>,
on_data_received: Rc<Option<web::Function>>,
set_size: Rc<Option<web::Function>>,
#[derivative(Debug = "ignore")]
object: Rc<java_script::binding::Visualization>,
#[derivative(Debug = "ignore")]
@ -104,8 +104,8 @@ impl InstanceModel {
styles.get_color(ensogl_hardcoded_theme::graph_editor::visualization::background)
}
fn create_root(scene: &Scene, logger: &Logger) -> result::Result<DomSymbol, Error> {
let div = web::create_div();
fn create_root(scene: &Scene) -> result::Result<DomSymbol, Error> {
let div = web::document.create_div_or_panic();
let root_node = DomSymbol::new(&div);
root_node
.dom()
@ -117,7 +117,7 @@ impl InstanceModel {
let bg_green = bg_color.green * 255.0;
let bg_blue = bg_color.blue * 255.0;
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)
}
@ -140,11 +140,12 @@ impl InstanceModel {
(closure_cell, closure)
}
#[cfg(target_arch = "wasm32")]
fn instantiate_class_with_args(
class: &JsValue,
args: JsConsArgs,
) -> 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 object = js_new
.call2(&context, class, &args.into())
@ -156,17 +157,25 @@ impl InstanceModel {
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.
pub fn from_class(class: &JsValue, scene: &Scene) -> result::Result<Self, Error> {
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 styles = StyleWatch::new(&scene.style_sheet);
let init_data = JsConsArgs::new(root_node.clone_ref(), styles, closure);
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 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 object = Rc::new(object);
let scene = scene.clone_ref();
@ -188,12 +197,17 @@ impl InstanceModel {
scene.manage(&self.root_node);
}
#[cfg(target_arch = "wasm32")]
fn set_size(&self, size: Vector2) {
let data_json = JsValue::from_serde(&size).unwrap();
let _ = self.try_call1(&self.set_size, &data_json);
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> {
let data_json = match data {
Data::Json { content } => content,
@ -209,15 +223,21 @@ impl InstanceModel {
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.
pub fn update_preprocessor(&self) -> result::Result<(), JsValue> {
self.object.emitPreprocessorChange()
}
#[cfg(target_arch = "wasm32")]
/// Helper method to call methods on the wrapped javascript object.
fn try_call1(
&self,
method: &Option<js_sys::Function>,
method: &Option<web::Function>,
arg: &JsValue,
) -> result::Result<(), JsValue> {
if let Some(method) = method {
@ -283,13 +303,11 @@ impl Instance {
let callback = move |preprocessor_config| change.emit(preprocessor_config);
let callback = Box::new(callback);
self.model.preprocessor_change.borrow_mut().replace(callback);
if let Err(js_error) = self.model.update_preprocessor() {
use enso_frp::web::js_to_string;
if let Err(err) = self.model.update_preprocessor() {
let logger = self.model.logger.clone();
error!(
logger,
"Failed to trigger initial preprocessor update from JS: \
{js_to_string(&js_error)}"
"Failed to trigger initial preprocessor update from JS: {err.print_to_string()}"
);
}
self
@ -311,10 +329,11 @@ impl display::Object for Instance {
// === Utils ===
#[cfg(target_arch = "wasm32")]
/// Try to return the method specified by the given name on the given object as a
/// `js_sys::Function`.
fn get_method(object: &js_sys::Object, property: &str) -> Result<js_sys::Function> {
let method_value = js_sys::Reflect::get(object, &property.into());
/// `web::Function`.
fn get_method(object: &web::Object, property: &str) -> Result<web::Function> {
let method_value = web::Reflect::get(object, &property.into());
let method_value = method_value.map_err(|object| Error::PropertyNotFoundOnObject {
object,
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();
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)
}
#[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::prelude::*;
use ensogl::system::web;
use ensogl::system::web::traits::*;
use ensogl::Animation;
use ensogl::DEPRECATED_Animation;
use ensogl::DEPRECATED_Tween;
@ -1623,7 +1624,7 @@ impl GraphEditorModel {
#[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs, always.
pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self {
let network = &frp.network;
let scene = app.display.scene();
let scene = &app.display.default_scene;
let logger = Logger::new("GraphEditor");
let display_object = display::object::Instance::new(&logger);
let nodes = Nodes::new(&logger);
@ -1687,7 +1688,7 @@ impl GraphEditorModel {
}
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)]
fn new_graph_editor(app: &Application) -> GraphEditor {
let world = &app.display;
let scene = world.scene();
let scene = &world.default_scene;
let cursor = &app.cursor;
let frp = Frp::new();
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_press <- viz_press_ev.gate_not(&viz_was_pressed);
viz_release <- viz_release_ev.gate(&viz_was_pressed);
viz_press_time <- viz_press . map(|_| web::performance().now() as f32);
viz_release_time <- viz_release . 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::window.performance_or_panic().now() as f32);
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_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 {
/// Create Code Editor component.
pub fn new(app: &Application) -> Self {
let scene = app.display.scene();
let styles = StyleWatchFrp::new(&app.display.scene().style_sheet);
let scene = &app.display.default_scene;
let styles = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let frp = Frp::new();
let network = &frp.network;
let model = app.new_view::<text::Area>();
@ -101,7 +101,7 @@ impl View {
frp.source.is_visible <+ frp.toggle.map2(&is_visible, |(),b| !b);
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| {
let height = height_f * scene_size.height;
let x = -scene_size.width / 2.0 + PADDING_LEFT;

View File

@ -61,8 +61,8 @@ impl PopupLabel {
let network = frp::Network::new("PopupLabel");
let label = Label::new(app);
label.set_opacity(0.0);
let background_layer = &app.display.scene().layers.panel;
let text_layer = &app.display.scene().layers.panel_text;
let background_layer = &app.display.default_scene.layers.panel;
let text_layer = &app.display.default_scene.layers.panel_text;
label.set_layers(background_layer, text_layer);
let opacity_animation = Animation::new(&network);
@ -175,7 +175,7 @@ impl View {
frp::extend! { network
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) {
let half_height = scene_size.height / 2.0;
let label_height = model.label_height();

View File

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

View File

@ -37,7 +37,7 @@ impl OpenDialog {
pub fn new(app: &Application) -> Self {
let logger = Logger::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 file_browser = FileBrowser::new();
// 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(&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;
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(&caption);
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.add_to_scene_layer(&app.display.scene().layers.panel_text);
list.set_label_layer(app.display.scene().layers.panel_text.id());
caption.add_to_scene_layer(&app.display.default_scene.layers.panel_text);
list.set_label_layer(app.display.default_scene.layers.panel_text.id());
ensogl::shapes_order_dependencies! {
app.display.scene() => {
app.display.default_scene => {
background -> list_view::selection;
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 height = style_watch.get_number(theme::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::system::web;
use ensogl::system::web::dom;
use ensogl::system::web::traits::*;
use ensogl::Animation;
use ensogl::DEPRECATED_Animation;
use ensogl_hardcoded_theme::Theme;
@ -145,7 +146,7 @@ struct Model {
impl Model {
fn new(app: &Application) -> Self {
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 searcher = app.new_view::<searcher::View>();
let graph_editor = app.new_view::<GraphEditor>();
@ -213,7 +214,7 @@ impl Model {
}
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> {
@ -369,7 +370,7 @@ impl View {
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 frp = Frp::new();
let searcher = &model.searcher.frp;

View File

@ -113,7 +113,7 @@ struct Model {
impl Model {
fn new(app: &Application) -> Self {
let scene = app.display.scene();
let scene = &app.display.default_scene;
let app = app.clone_ref();
let logger = Logger::new("SearcherView");
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
// 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 = style.get_number_or(action_list_gap_path, 0.0);
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::DomSymbol;
use ensogl::system::web;
use ensogl::system::web::StyleSetter;
use ensogl::system::web::traits::*;
use ensogl_hardcoded_theme::application::searcher::icons as theme;
use std::f32::consts::PI;
use wasm_bindgen::prelude::*;
@ -998,27 +998,24 @@ mod frame {
#[wasm_bindgen]
#[allow(dead_code)]
pub fn entry_point_searcher_icons() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
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::light::register(&app);
ensogl_hardcoded_theme::builtin::light::enable(&app);
let world = app.display.clone();
mem::forget(app);
let scene = world.scene();
let scene = &world.default_scene;
mem::forget(Navigator::new(scene, &scene.camera()));
// === Grid ===
let grid_div = web::create_div();
grid_div.set_style_or_panic("width", "1000px");
grid_div.set_style_or_panic("height", "16px");
grid_div.set_style_or_panic("background-size", "1.0px 1.0px");
grid_div.set_style_or_panic(
let grid_div = web::document.create_div_or_panic();
grid_div.set_style_or_warn("width", "1000px");
grid_div.set_style_or_warn("height", "16px");
grid_div.set_style_or_warn("background-size", "1.0px 1.0px");
grid_div.set_style_or_warn(
"background-image",
"linear-gradient(to right, 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 {
fn new(app: &Application) -> Self {
let scene = app.display.scene();
let scene = &app.display.default_scene;
let logger = Logger::new("StatusBar");
let display_object = 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);
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);
label.frp.set_color_all.emit(text_color);
label.frp.set_default_color.emit(text_color);
@ -279,7 +279,7 @@ impl View {
let frp = Frp::new();
let model = Model::new(app);
let network = &frp.network;
let scene = app.display.scene();
let scene = &app.display.default_scene;
enso_frp::extend! { network
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);
ensogl::shapes_order_dependencies! {
app.display.scene() => {
app.display.default_scene => {
shape -> close::shape;
shape -> fullscreen::shape;
}
@ -233,7 +233,7 @@ impl View {
let model = Model::new(app);
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 layout_style = style_frp.flatten(network);
let radius = style.get_number(theme::radius);

View File

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

View File

@ -19,14 +19,12 @@ use ensogl::application::Application;
use ensogl::display;
use ensogl::display::DomSymbol;
use ensogl::system::web;
use ensogl::system::web::NodeInserter;
use ensogl::system::web::StyleSetter;
use ensogl::system::web::traits::*;
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
use web_sys::Element;
use web_sys::HtmlDivElement;
use web_sys::MouseEvent;
use web::Closure;
use web::Element;
use web::HtmlDivElement;
use web::MouseEvent;
@ -83,26 +81,23 @@ impl Deref for ClickableElement {
}
impl ClickableElement {
pub fn new(element: Element, logger: &Logger) -> Self {
pub fn new(element: Element) -> Self {
frp::new_network! { network
click <- source_();
}
let closure: ClickClosure = Closure::wrap(Box::new(f_!(click.emit(()))));
let callback = closure.as_ref().unchecked_ref();
if element.add_event_listener_with_callback("click", callback).is_err() {
error!(logger, "Could not add event listener for ClickableElement.");
}
network.store(&Rc::new(closure));
let closure: ClickClosure = Closure::new(f_!(click.emit(())));
let handle = web::add_event_listener(&element, "click", closure);
network.store(&handle);
Self { element, network, click }
}
}
// =============
// === Model ===
// =============
// === CSS Styles ===
static STYLESHEET: &str = include_str!("../style.css");
@ -130,42 +125,37 @@ impl Model {
let side_menu = SideMenu::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);
// 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);
dom.append_or_warn(&style, &logger);
dom.append_or_warn(&style);
Self { application, logger, dom, display_object, side_menu, template_cards }
}
fn create_dom(
logger: &Logger,
side_menu: &SideMenu,
template_cards: &TemplateCards,
) -> DomSymbol {
let root = web::create_div();
fn create_dom(side_menu: &SideMenu, template_cards: &TemplateCards) -> DomSymbol {
let root = web::document.create_div_or_panic();
root.set_class_name(css_class::TEMPLATES_VIEW_ROOT);
// We explicitly enable pointer events for Welcome Screen elements. Pointer events are
// 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();
container.append_or_warn(&side_menu.model.root_dom, logger);
container.append_or_warn(&template_cards.model.root_dom, logger);
root.append_or_warn(&container, logger);
container.append_or_warn(&side_menu.model.root_dom);
container.append_or_warn(&template_cards.model.root_dom);
root.append_or_warn(&container);
DomSymbol::new(&root)
}
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
}
}
@ -220,7 +210,7 @@ impl View {
frp::extend! { network
// === 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)));
}
frp::extend! { network

View File

@ -8,34 +8,33 @@ use crate::ClickableElement;
use enso_frp as frp;
use ensogl::system::web;
use ensogl::system::web::NodeInserter;
use web_sys::Element;
use ensogl::system::web::traits::*;
// ================
// =============
// === Model ===
// ================
// =============
#[derive(Clone, CloneRef, Debug)]
pub struct Model {
logger: Logger,
pub root_dom: Element,
pub root_dom: web::Element,
new_project_button: ClickableElement,
projects_list_dom: Element,
projects_list_dom: web::Element,
projects: Rc<RefCell<Vec<ClickableElement>>>,
}
impl Model {
/// Constructor.
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);
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();
root_dom.append_or_warn(&projects_list_dom, &logger);
let new_project_button = Self::create_new_project_button(&logger, &projects_list_dom);
root_dom.append_or_warn(&projects_list_dom);
let new_project_button = Self::create_new_project_button(&projects_list_dom);
let projects = default();
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>) {
let entry = Self::create_project_list_entry(name, &self.logger);
let entry = Self::create_project_list_entry(name);
let network = &entry.network;
frp::extend! { network
open_project <+ entry.click.constant(name.to_owned());
}
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);
}
fn create_new_project_button(logger: &Logger, projects_list: &Element) -> ClickableElement {
let element = web::create_element("li");
fn create_new_project_button(projects_list: &web::Element) -> ClickableElement {
let element = web::document.create_element_or_panic("li");
element.set_id(crate::css_id::NEW_PROJECT);
element.set_inner_html(r#"<img src="/assets/new-project.svg" />Create a new project"#);
projects_list.append_or_warn(&element, logger);
ClickableElement::new(element, logger)
projects_list.append_or_warn(&element);
ClickableElement::new(element)
}
fn create_header(text: &str) -> Element {
let header = web::create_element("h2");
fn create_header(text: &str) -> web::Element {
let header = web::document.create_element_or_panic("h2");
header.set_text_content(Some(text));
header
}
fn create_projects_list() -> Element {
web::create_element("ul")
fn create_projects_list() -> web::Element {
web::document.create_element_or_panic("ul")
}
fn create_project_list_entry(project_name: &str, logger: &Logger) -> ClickableElement {
let element = web::create_element("li");
fn create_project_list_entry(project_name: &str) -> ClickableElement {
let element = web::document.create_element_or_panic("li");
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 ensogl::system::web;
use ensogl::system::web::AttributeSetter;
use ensogl::system::web::NodeInserter;
use wasm_bindgen::JsCast;
use web_sys::Element;
use web_sys::HtmlDivElement;
use ensogl::system::web::traits::*;
use web::Element;
use web::HtmlDivElement;
use web::JsCast;
@ -88,16 +87,16 @@ pub struct Model {
impl Model {
/// Constructor.
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);
let templates = web::create_div();
let templates = web::document.create_div_or_panic();
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);
templates.append_or_warn(&cards_dom, &logger);
root_dom.append_or_warn(&templates, &logger);
let (cards_dom, cards) = Self::create_cards();
templates.append_or_warn(&cards_dom);
root_dom.append_or_warn(&templates);
let model = Self { logger, root_dom, cards: Rc::new(cards) };
model.setup_event_listeners(open_template);
@ -116,58 +115,54 @@ impl Model {
}
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
}
/// 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 dom = web::create_div();
let dom = web::document.create_div_or_panic();
dom.set_class_name(crate::css_class::CARDS);
let row1 = Self::create_row(&[CARD_SPREADSHEETS, CARD_GEO], &mut cards, logger);
dom.append_or_warn(&row1, logger);
let row1 = Self::create_row(&[CARD_SPREADSHEETS, CARD_GEO], &mut cards);
dom.append_or_warn(&row1);
let row2 = Self::create_row(&[CARD_VISUALIZE], &mut cards, logger);
dom.append_or_warn(&row2, logger);
let row2 = Self::create_row(&[CARD_VISUALIZE], &mut cards);
dom.append_or_warn(&row2);
(dom, cards)
}
fn create_row(
definitions: &[CardDefinition],
cards: &mut Vec<Card>,
logger: &Logger,
) -> HtmlDivElement {
let row = web::create_div();
fn create_row(definitions: &[CardDefinition], cards: &mut Vec<Card>) -> HtmlDivElement {
let row = web::document.create_div_or_panic();
row.set_class_name(crate::css_class::ROW);
for definition in definitions {
let card = Self::create_card(definition, logger);
row.append_or_warn(&card.element, logger);
let card = Self::create_card(definition);
row.append_or_warn(&card.element);
cards.push(card.clone());
}
row
}
/// Helper to create a single card DOM from provided definition.
fn create_card(definition: &CardDefinition, logger: &Logger) -> Card {
let card = web::create_div();
fn create_card(definition: &CardDefinition) -> Card {
let card = web::document.create_div_or_panic();
card.set_class_name(&format!("{} {}", crate::css_class::CARD, definition.class));
if let Some(src) = definition.background_image_url {
let img = web::create_element("img");
img.set_attribute_or_warn("src", src, logger);
card.append_or_warn(&img, logger);
let img = web::document.create_element_or_panic("img");
img.set_attribute_or_warn("src", src);
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.append_or_warn(&card_header, logger);
let text_content = web::create_element("p");
card.append_or_warn(&card_header);
let text_content = web::document.create_element_or_panic("p");
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 }
}
}

View File

@ -188,7 +188,7 @@ function printScamWarning() {
'copy-paste something here, it is a scam and will give them access to your ' +
'account and data.'
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.'
console.log('%cStop!', headerCSS1)
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::initializer::setup_global_executor;
use enso_gui::Ide;
use enso_web::traits::*;
use enso_web::HtmlDivElement;
use enso_web::NodeInserter;
use enso_web::StyleSetter;
use ensogl::application::Application;
/// Reexports of commonly-used structures, methods and traits.
@ -57,12 +56,11 @@ impl IntegrationTest {
/// Initializes the executor and `Ide` structure and returns new Fixture.
pub async fn setup() -> Self {
enso_web::forward_panic_hook_to_error();
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_style_or_panic("display", "none");
enso_web::body().append_or_panic(&root_div);
root_div.set_style_or_warn("display", "none");
enso_web::document.body_or_panic().append_or_warn(&root_div);
let initializer = enso_gui::ide::Initializer::new(default());
let ide = initializer.start().await.expect("Failed to initialize the application.");
@ -72,7 +70,7 @@ impl IntegrationTest {
fn set_screen_size(app: &Application) {
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)
});
}

View File

@ -75,7 +75,7 @@ async fn zooming() {
let test = IntegrationTestOnNewProject::setup().await;
let project = test.project_view();
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 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);
// 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));
let wait_for_update = Duration::from_millis(500);
sleep(wait_for_update).await;
@ -145,8 +145,8 @@ async fn adding_node_with_add_node_button() {
assert!(node_source.is_none());
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 center_of_screen =
test.ide.ensogl_app.display.scene().screen_to_scene_coordinates(Vector3(0.0, 0.0, 0.0));
let scene = &test.ide.ensogl_app.display.default_scene;
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.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 std::any::TypeId;
use std::marker::Unsize;
// ================
// === Callback ===
// ================
// ==============================
// === Popular Callback Types ===
// ==============================
/// Immutable callback type.
pub trait CallbackFn = Fn() + 'static;
pub use callback_types::*;
/// Immutable callback object.
pub type Callback = Box<dyn CallbackFn>;
/// Popular callback types. These are aliases for static [`Fn`] and [`FnMut`] with different amount
/// 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.
#[allow(non_snake_case)]
pub fn Callback<F: CallbackFn>(f: F) -> Callback {
Box::new(f)
pub trait Copy1<T1> = 'static + Fn(T1);
pub trait Copy2<T1, T2> = 'static + Fn(T1, T2);
pub trait Copy3<T1, T2, T3> = 'static + Fn(T1, T2, T3);
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.
#[derive(Clone, CloneRef, Debug)]
#[derive(Clone, CloneRef, Debug, Default)]
pub struct Handle {
rc: Rc<Cell<bool>>,
is_invalidated: Rc<Cell<bool>>,
}
impl Handle {
/// Constructor.
pub fn new() -> Self {
let rc = Rc::new(Cell::new(true));
Self { rc }
}
/// Create guard for this handle.
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
/// run anymore after performing this operation.
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
@ -79,12 +92,6 @@ impl Handle {
}
}
impl Default for Handle {
fn default() -> Self {
Self::new()
}
}
// =============
@ -100,10 +107,75 @@ pub struct Guard {
impl Guard {
/// Checks if the handle is still valid.
pub fn exists(&self) -> bool {
match self.weak.upgrade() {
None => false,
Some(t) => t.get(),
}
self.weak.upgrade().map_or(false, |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 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(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.
/// The main callback registry structure. The [`F`] parameter is either instantiated to
/// [`RegistryFn<dyn Fn<Args>>`] or to [`RegistryFnMut<dyn FnMut<Args>>`]. See the generated aliases
/// for common types below, in the [`registry`] module.
#[derive(CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
#[derivative(Debug(bound = ""))]
#[derivative(Default(bound = ""))]
#[allow(clippy::type_complexity)]
pub struct SharedRegistryMut1<T> {
#[allow(missing_docs)]
pub struct Registry<F> {
#[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.
pub fn new() -> Self {
Self::default()
}
/// Adds new callback and returns a new handle for it.
pub fn add<F: CallbackMut1Fn<T>>(&self, callback: F) -> Handle {
let callback = Rc::new(RefCell::new(callback));
let handle = Handle::new();
/// Add a new callback. Returns a new [`Handle`], which dropped, will unregister the callback.
pub fn add<C>(&self, callback: C) -> Handle
where
F: RegistryFnNew,
C: Unsize<<F as RegistryFnNew>::InternalF> + 'static, {
let callback = F::new(callback);
let handle = Handle::default();
let guard = handle.guard();
self.callback_list.borrow_mut().push((guard, callback));
handle
@ -234,103 +221,164 @@ impl<T> SharedRegistryMut1<T> {
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.
fn clear_unused_callbacks(&self) {
self.callback_list.borrow_mut().retain(|(guard, _)| guard.exists());
}
}
// =================
// === Registry1 ===
// =================
/// 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) {
/// 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)
where F: Clone + RegistryFnCall<Args> {
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.
fn clear_unused_callbacks(&mut self) {
self.callback_list.retain(|(guard, _)| guard.exists());
}
/// Aliases for common [`Registry`] instantiations. The names directly correspond to the
/// [`::callback_types`] namespace. For example, the [`::registry::CopyMut3`] is a callback registry
/// 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
/// as values is more performant than by reference.
#[derive(Derivative)]
#[derivative(Debug(bound = ""), Default(bound = ""))]
pub struct CopyRegistry1<T> {
#[derivative(Debug = "ignore")]
callback_list: Vec<(Guard, CopyCallbackMut1<T>)>,
/// Generator of traits allowing the usage of a [`run_all`] function. It is an alias for the
/// [`Registry::run_all_with_args`] where arguments are passed in a convenient way, instead than in
/// a tuple.
macro_rules! gen_runner_traits {
($name:ident, $ref_name:ident, ($($arg:ident),*)) => {
#[allow(non_snake_case)]
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> {
/// Adds new callback and returns a new handle for it.
pub fn add<F: CopyCallbackMut1Fn<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
macro_rules! gen_runner {
($name:ident, $ref_name:ident, $data:ident, $data_ref:ident, <$($arg:ident),*>) => {
#[allow(non_snake_case)]
impl<$($arg: Copy),*> $name for registry::$data<$($arg),*> {
$(type $arg = $arg;)*
fn run_all(&self, $($arg : Self::$arg),*) {
self.run_all_with_args(($($arg),*,))
}
}
#[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.
pub fn is_empty(&self) -> bool {
self.callback_list.is_empty()
impl RegistryRunner0 for registry::MutNoArgs {
fn run_all(&self) {
self.run_all_with_args(())
}
}
/// Fires all registered callbacks and removes the ones which got dropped.
pub fn run_all(&mut self, t: T) {
self.clear_unused_callbacks();
self.callback_list.iter_mut().for_each(move |(_, callback)| callback(t));
impl RegistryRunner0 for registry::NoArgs {
fn run_all(&self) {
self.run_all_with_args(())
}
}
/// Checks all registered callbacks and removes the ones which got dropped.
fn clear_unused_callbacks(&mut self) {
self.callback_list.retain(|(guard, _)| guard.exists());
}
gen_runner_traits!(RegistryRunner1, RegistryRunnerRef1, (T1));
gen_runner_traits!(RegistryRunner2, RegistryRunnerRef2, (T1, T2));
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)]
pub struct DynEventDispatcher {
#[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 {
/// 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| {
event.any.downcast_ref::<T>().iter().for_each(|t| f(t))
});
let type_id = (&PhantomData::<T>).type_id();
let handle = Handle::new();
let handle = Handle::default();
let guard = handle.guard();
let listeners = self.listener_map.entry(type_id).or_insert_with(default);
listeners.push((guard, callback));

View File

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

View File

@ -220,7 +220,7 @@ impl DropDownMenu {
let frp = &self.frp;
let model = &self.model;
let scene = app.display.scene();
let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp;
frp::extend! { network
@ -368,7 +368,7 @@ impl DropDownMenu {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for
// 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);
model.label.set_default_color(text_color);

View File

@ -10,7 +10,7 @@ enso-frp = { path = "../../../frp" }
enso-logger = { path = "../../../logger"}
enso-prelude = { path = "../../../prelude"}
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" }
[dependencies.web-sys]

View File

@ -21,12 +21,16 @@ pub mod prelude {
use crate::prelude::*;
use enso_frp as frp;
use enso_web as web;
use enso_web::stream::BlobExt;
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 wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::JsFuture;
@ -54,7 +58,7 @@ pub struct File {
impl 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 size = file.size() as u64;
let mime_type = ImString::new(file.type_());
@ -64,6 +68,7 @@ impl File {
Ok(File { name, mime_type, size, reader })
}
#[cfg(target_arch = "wasm32")]
/// Read the next chunk of file content.
///
/// 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.
/// See https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read and
/// 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 {
let js_result = JsFuture::from(reader.read()).await?;
let is_done = js_sys::Reflect::get(&js_result, &"done".into())?.as_bool().unwrap();
@ -86,6 +91,12 @@ impl File {
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)]
pub struct Manager {
#[allow(dead_code)]
network: frp::Network,
files_received: frp::Source<Vec<File>>,
network: frp::Network,
files_received: frp::Source<Vec<File>>,
#[allow(dead_code)]
drop_callback: Rc<DropClosure>,
drop_handle: web::EventListenerHandle,
#[allow(dead_code)]
drag_over_callback: Rc<DragOverClosure>,
drag_over_handle: web::EventListenerHandle,
}
impl Manager {
/// 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");
debug!(logger, "Creating DropFileManager");
debug!(logger, "Creating");
let network = frp::Network::new("DropFileManager");
frp::extend! { network
files_received <- source();
}
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.");
event.prevent_default();
Self::handle_drop_event(&logger,event,&files_received)
})));
}));
// To mark element as a valid drop target, the `dragover` event handler should return
// `false`. See
// 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();
false
}));
let drop_js = drop.as_ref().unchecked_ref();
let drag_over_js = drag_over.as_ref().unchecked_ref();
target.add_event_listener_with_callback("drop", drop_js).unwrap();
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 }
});
let drop_handle = web::add_event_listener(target, "drop", drop);
let drag_over_handle = web::add_event_listener(target, "dragover", drag_over);
Self { network, files_received, drop_handle, drag_over_handle }
}
/// 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) {
Ok(file) => Some(file),
Err(err) => {
error!(logger, "Error when processing dropped file: {err:?}");
error!(logger, "Error when processing dropped file: {err:?}.");
None
}
});

View File

@ -112,7 +112,7 @@ impl component::Model for Model {
}
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 label = app.new_view::<text::Area>();
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 model = Rc::new(M::new(&app, &logger));
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);
let frp = Rc::new(frp);
Self { frp, model, app, logger }

View File

@ -86,7 +86,7 @@ struct Model {
impl Model {
fn new(app: Application) -> Self {
let app = app.clone_ref();
let scene = app.display.scene();
let scene = &app.display.default_scene;
let logger = Logger::new("TextLabel");
let display_object = display::object::Instance::new(&logger);
let label = app.new_view::<text::Area>();
@ -95,7 +95,7 @@ impl Model {
display_object.add_child(&background);
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 };
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 label = app.new_view::<ensogl_text::Area>();
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 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 display_object = display::object::Instance::new(&logger);
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 }
}
@ -183,7 +183,9 @@ where E::Model: Default
/// Sets the scene layer where the labels will be placed.
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() {
entry.entry.set_label_layer(&layer);
}
@ -198,7 +200,7 @@ where E::Model: Default
}
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(|| {
error!(
self.logger,

View File

@ -146,7 +146,7 @@ impl<E: Entry> Model<E> {
fn padding(&self) -> f32 {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// 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)
}
@ -188,7 +188,7 @@ impl<E: Entry> Model<E> {
/// Check if the `point` is inside component assuming that it have given `size`.
fn is_inside(&self, point: Vector2<f32>, size: Vector2<f32>) -> bool {
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 y_range = (-size.y / 2.0)..=(size.y / 2.0);
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 network = &frp.network;
let model = &self.model;
let scene = app.display.scene();
let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp;
let view_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 {
/// Create a new scroll area for use in the given application.
pub fn new(app: &Application) -> ScrollArea {
let scene = app.display.scene();
let scene = &app.display.default_scene;
let logger = Logger::new("ScrollArea");
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) {
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp;
let thumb_position = Animation::new(network);
let thumb_color = color::Animation::new(network);
@ -304,7 +304,7 @@ impl Scrollbar {
let app = app.clone_ref();
let model = Rc::new(Model::new(&app));
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);
let frp = Rc::new(frp);
Self { frp, model, app }

View File

@ -58,8 +58,8 @@ impl Model {
let label_full = 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.add_to_scene_layer(&app.display.scene().layers.label);
label_full.remove_from_scene_layer(&app.display.default_scene.layers.main);
label_full.add_to_scene_layer(&app.display.default_scene.layers.label);
root.add_child(&label_full);
root.add_child(&label_left);

View File

@ -75,24 +75,20 @@ impl Frp {
size: frp::Stream<Vector2>,
mouse: &Mouse,
) -> Frp {
let net = &network;
let scene = &model.app.display.default_scene;
let shadow = shadow::frp_from_style(style, theme::shadow);
let text_size = style.get_number(theme::text::size);
let is_dragging_left_overflow =
shape_is_dragged(network, &model.left_overflow.events, mouse);
let is_dragging_right_overflow =
shape_is_dragged(network, &model.right_overflow.events, mouse);
let is_dragging_track = shape_is_dragged(network, &model.track.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_left_overflow = shape_is_dragged(net, &model.left_overflow.events, mouse);
let is_dragging_right_overflow = shape_is_dragged(net, &model.right_overflow.events, mouse);
let is_dragging_track = shape_is_dragged(net, &model.track.events, mouse);
let is_dragging_background = shape_is_dragged(net, &model.background.events, mouse);
let is_dragging_left_handle = shape_is_dragged(net, &model.track_handle_left.events, mouse);
let is_dragging_right_handle =
shape_is_dragged(network, &model.track_handle_right.events, mouse);
let background_click =
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);
shape_is_dragged(net, &model.track_handle_right.events, mouse);
let background_click = relative_shape_down_position(net, scene, &model.background);
let track_click = relative_shape_down_position(net, scene, &model.track);
// Initialisation of components. Required for correct layout on startup.
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 model = Rc::new(Model::new(&app));
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);
let frp = Rc::new(frp);
Self { frp, model, app }
@ -146,7 +146,7 @@ impl NumberRangePicker {
let app = app.clone_ref();
let model = Rc::new(Model::new(&app));
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);
let frp = Rc::new(frp);
Self { frp, model, app }

View File

@ -97,7 +97,7 @@ impl Model {
let padding = default();
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::<track::View, left_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) {
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp;
model.show_background(true);
@ -55,11 +55,8 @@ impl Frp {
let track_shape_system = scene.shapes.shape_system(PhantomData::<track::Shape>);
track_shape_system.shape_system.set_pointer_events(false);
let background_click =
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);
let background_click = relative_shape_down_position(network, scene, &model.background);
let track_click = relative_shape_down_position(network, scene, &model.track);
let style_track_color = style.get_color(theme::component::slider::track::color);
frp::extend! { network

View File

@ -41,7 +41,7 @@ impl Frp {
pub fn init(&self, app: &Application, model: &Model, style: &StyleWatchFrp) {
let frp = &self;
let network = &frp.network;
let scene = app.display.scene();
let scene = &app.display.default_scene;
let mouse = &scene.mouse.frp;
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::DomSymbol;
use ensogl_core::frp;
use ensogl_core::system::web::StyleSetter;
use ensogl_core::system::web::traits::*;
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`.
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_y = -style.get_number(theme::shadow::offset_y);
let alpha = style.get_number(ensogl_hardcoded_theme::shadow::html::alpha);
let blur = style.get_number(ensogl_hardcoded_theme::shadow::html::blur);
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);
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"}
js-sys = { version = "0.3" }
nalgebra = { version = "0.26.1" }
wasm-bindgen = { version = "0.2.58" }
wasm-bindgen = { version = "0.2.78" }
[dev-dependencies]
wasm-bindgen-test = { version = "0.3.8" }

View File

@ -304,7 +304,7 @@ impl Area {
fn init(self) -> Self {
let network = &self.frp.network;
let model = &self.data;
let scene = model.app.display.scene();
let scene = &model.app.display.default_scene;
let mouse = &scene.mouse.frp;
let input = &self.frp.input;
let out = &self.frp.output;
@ -532,7 +532,7 @@ impl Area {
fn symbols(&self) -> SmallVec<[display::Symbol; 1]> {
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_symbol = selection_system.shape_system.symbol.clone_ref();
//TODO[ao] we cannot move selection symbol, as it is global for all the text areas.
@ -568,7 +568,7 @@ impl AreaModel {
/// Constructor.
pub fn new(app: &Application, frp_endpoints: &FrpEndpoints) -> Self {
let app = app.clone_ref();
let scene = app.display.scene();
let scene = &app.display.default_scene;
let logger = Logger::new("text_area");
let selection_map = default();
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 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_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;

View File

@ -102,7 +102,9 @@ impl System {
let logger = Logger::new("glyph_system");
let size = font::msdf::Texture::size();
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 symbol = sprite_system.symbol();
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,
# 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]
version = "0.3.4"

View File

@ -1,12 +1,11 @@
//! This module contains implementation of `DynamicLoop`, a loop manager which runs a
//! `DynamicLoopCallback` once per frame.
//! This module contains implementation of loops mainly used for per-frame callbacks firing.
use crate::prelude::*;
use crate::control::callback;
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 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));
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;
Self { data }
}
@ -100,14 +101,14 @@ impl<Callback> RawLoopData<Callback> {
let callback = &mut self.callback;
self.handle_id = self.on_frame.as_ref().map_or(default(), |on_frame| {
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> {
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))
}
}
// ===================
// === 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::display;
use crate::display::scene::DomPath;
use crate::display::style::theme;
use crate::display::world::World;
use crate::gui::cursor::Cursor;
use crate::system::web;
use enso_web::StyleSetter;
use enso_web::traits::*;
@ -41,19 +42,20 @@ pub struct Application {
impl Application {
/// Constructor.
pub fn new(dom: &web_sys::HtmlElement) -> Self {
pub fn new(dom: impl DomPath) -> Self {
let logger = Logger::new("Application");
let display = World::new(dom);
let scene = display.scene();
let display = World::new();
let scene = &display.default_scene;
scene.display_in(dom);
let commands = command::Registry::create(&logger);
let shortcuts =
shortcut::Registry::new(&logger, &scene.mouse.frp, &scene.keyboard.frp, &commands);
let views = view::Registry::create(&logger, &display, &commands, &shortcuts);
let themes = theme::Manager::from(&display.scene().style_sheet);
let cursor = Cursor::new(display.scene());
let themes = theme::Manager::from(&display.default_scene.style_sheet);
let cursor = Cursor::new(&display.default_scene);
display.add_child(&cursor);
web::body().set_style_or_panic("cursor", "none");
let update_themes_handle = display.on_before_frame(f_!(themes.update()));
web::document.body_or_panic().set_style_or_warn("cursor", "none");
let update_themes_handle = display.on.before_frame.add(f_!(themes.update()));
Self { logger, cursor, display, commands, shortcuts, views, themes, update_themes_handle }
}
@ -74,3 +76,18 @@ impl AsRef<theme::Manager> for Application {
&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_rules! read_args {
([$($($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.
#[derive(Clone,Copy,Debug,Default)]
pub struct ArgNames;
impl ArgNames {
$(
/// Name of the field.
pub fn $field(&self) -> &'static str {
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()
/// Reflection mechanism containing string representation of option names.
#[derive(Clone,Copy,Debug,Default)]
pub struct ArgNames;
impl ArgNames {
$(
/// Name of the field.
pub fn $field(&self) -> &'static str {
stringify!{$field}
}
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
/// does nothing, however, in order to call it, the user would need to access a field in
/// the lazy static variable `ARGS`, which would trigger argument parsing process.
pub fn init(&self) {}
/// The structure containing application configs.
#[derive(Clone,Debug,Default)]
#[allow(missing_docs)]
pub struct Args {
$(pub $field : Option<$field_type>),*
}
/// Reflection mechanism to get string representation of argument names.
pub fn names(&self) -> ArgNames { ArgNames }
}
impl Args {
/// 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! {
/// Application arguments initialized in a lazy way (on first read).
pub static ref ARGS : Args = Args::new();
/// This is a dummy function which initializes the arg reading process. This
/// function does nothing, however, in order to call it, the user would need to
/// 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;
use crate::control::callback;
use crate::control::callback::traits::*;
use crate::system::web;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web::Closure;
use web::JsCast;
use web::JsValue;
pub use crate::frp::io::mouse::*;
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 ===
// ====================
/// 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.
#[derive(Clone, CloneRef, Debug, Shrinkwrap)]
pub struct MouseManager {
#[shrinkwrap(main_field)]
dispatchers: MouseManagerDispatchers,
closures: Rc<MouseManagerClosures>,
handles: Rc<MouseManagerEventListenerHandles>,
dom: web::dom::WithKnownShape<web::EventTarget>,
}
/// 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 {
( $( $js_event:ident :: $js_name:ident => $name:ident ($target:ident) ),* $(,)? ) => {
/// Keeps references to JavaScript closures in order to keep them alive.
#[derive(Debug)]
pub struct MouseManagerClosures {
target : web::EventTarget,
$($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) }
)*
}
pub struct MouseManagerEventListenerHandles {
$($name : web::EventListenerHandle),*
}
/// Set of dispatchers for various mouse events.
#[derive(Clone,CloneRef,Debug,Default)]
#[allow(missing_docs)]
pub struct MouseManagerDispatchers {
$(pub $name : EventDispatcher<$target>),*
$(pub $name : callback::registry::RefMut1<$target>),*
}
impl MouseManager {
@ -101,41 +59,36 @@ macro_rules! define_bindings {
/// 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
/// some html element may be passed as a size provider, and another one where we attach
/// listeners (for example `body` and `window` respectively).
/// Sometimes we want to listen for mouse event for element without ResizeObserver.
/// Thus, some html element may be passed as a size provider, and another one where we
/// attach listeners (for example `body` and `window` respectively).
pub fn new_separated
(dom:&web::dom::WithKnownShape<web::EventTarget>,target:&web::EventTarget) -> Self {
let dispatchers = MouseManagerDispatchers::default();
let dom = dom.clone();
let target = target.clone();
let dom = dom.clone();
$(
let shape = dom.shape.clone_ref();
let shape = dom.shape.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 event = event.unchecked_into::<web_sys::$js_event>();
dispatcher.dispatch(&event::$target::new(event,shape))
}));
let js_closure = $name.as_ref().unchecked_ref();
let js_name = stringify!($js_name);
let options = event_listener_options();
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 event = event.unchecked_into::<web::$js_event>();
dispatcher.run_all(&event::$target::new(event,shape))
});
let js_name = stringify!($js_name);
let opt = event_listener_options();
let $name = web::add_event_listener_with_options(&target,js_name,closure,&opt);
)*
let closures = Rc::new(MouseManagerClosures {target,$($name),*});
Self {dispatchers,closures,dom}
let handles = Rc::new(MouseManagerEventListenerHandles {$($name),*});
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
fn event_listener_options() -> web_sys::AddEventListenerOptions {
let mut options = web_sys::AddEventListenerOptions::new();
fn event_listener_options() -> web::AddEventListenerOptions {
let mut options = web::AddEventListenerOptions::new();
// We listen for events in capture phase, so we can decide ourself if it should be passed
// further.
options.capture(true);

View File

@ -2,10 +2,11 @@
use crate::prelude::*;
use crate::system::web::dom::Shape;
use crate::system::web;
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)]
pub struct $name {
#[shrinkwrap(main_field)]
raw : web_sys::$js_event,
raw : web::$js_event,
shape : Shape,
}
impl $name {
/// 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}
}
@ -66,15 +67,15 @@ macro_rules! define_events {
/// 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.
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()?;
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.
///
/// 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 x = self.client_x() as f64 - rect.left();
let y = self.client_y() as f64 - rect.top();
@ -83,9 +84,9 @@ macro_rules! define_events {
}
impl AsRef<web_sys::Event> for $name {
fn as_ref(&self) -> &web_sys::Event {
let js_event = AsRef::<web_sys::$js_event>::as_ref(self);
impl AsRef<web::Event> for $name {
fn as_ref(&self) -> &web::Event {
let js_event = AsRef::<web::$js_event>::as_ref(self);
js_event.as_ref()
}
}

View File

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

View File

@ -4,6 +4,7 @@
use crate::prelude::*;
use crate::control::callback;
use crate::control::callback::traits::*;
use crate::data::dirty;
use crate::data::dirty::traits::*;
use crate::display;
@ -158,10 +159,10 @@ impl Default for Matrix {
// ====================
/// 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.
pub trait ZoomUpdateFn = callback::CallbackMut1Fn<f32>;
pub trait ZoomUpdateFn = Fn(f32) + 'static;
/// Internal `Camera2d` representation. Please see `Camera2d` for full documentation.
#[derive(Debug)]
@ -174,8 +175,8 @@ struct Camera2dData {
clipping: Clipping,
matrix: Matrix,
dirty: Dirty,
zoom_update_registry: callback::Registry1<f32>,
screen_update_registry: callback::Registry1<Vector2<f32>>,
zoom_update_registry: callback::registry::CopyMut1<f32>,
screen_update_registry: callback::registry::CopyMut1<Vector2<f32>>,
}
type ProjectionDirty = dirty::SharedBool<()>;
@ -271,8 +272,7 @@ impl Camera2dData {
}
if changed {
self.matrix.view_projection = self.matrix.projection * self.matrix.view;
let zoom = self.zoom;
self.zoom_update_registry.run_all(&zoom);
self.zoom_update_registry.run_all(self.zoom);
}
changed
}
@ -311,7 +311,7 @@ impl Camera2dData {
_ => unimplemented!(),
};
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) {
@ -406,6 +406,8 @@ impl Camera2d {
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.
pub fn add_zoom_update_callback<F: ZoomUpdateFn>(&self, f: F) -> callback::Handle {
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 pan_speed = pan_speed.get().into_on().unwrap_or(0.0);
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);
});
let resize_callback = camera.add_screen_update_callback(
enclose!((mut simulator,camera) move |_:&Vector2<f32>| {
let resize_callback =
camera.add_screen_update_callback(enclose!((mut simulator,camera) move |_| {
let position = camera.position();
simulator.set_value(position);
simulator.set_target_value(position);
simulator.set_velocity(default());
}),
);
}));
let zoom_callback = f!([camera,simulator,max_zoom] (zoom:ZoomEvent) {
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,
/// list of children, and set of utils for dirty flag propagation.
///
/// ## Host
/// 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.
/// See the documentation of [`Instance`] to learn more.
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
pub struct Model<Host = Scene> {
@ -610,18 +596,33 @@ pub struct Id(usize);
/// list of children, and set of utils for dirty flag propagation.
///
/// ## Host
/// The structure is parametrized with a `Host`. In real life use cases, host will be instantiated
/// with [`Scene`]. For simplicity, it is instantiated to empty tuple in tests. 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.
/// The model is parametrized with a `Host`. In real life use cases, host is **ALWAYS** 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 rendering. After adding a display object
/// to a scene, a new sprite can be created to display it visually. After removing the object and
/// adding it to a different scene (another GPU context), the sprite in the first context can be
/// trashed, and a new sprite in the new context can be created instead. This mechanism is currently
/// also used for sprites layer management, but this functionality is inherently connected to the
/// 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
/// 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
/// to be assigned to a particular [`scene::Layer`], and thus allows for easy to use depth
/// 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(CloneRef)]
#[derivative(Clone(bound = ""))]

View File

@ -10,6 +10,7 @@ pub use layer::Layer;
pub use crate::system::web::dom::Shape;
use crate::prelude::*;
use web::traits::*;
use crate::animation;
use crate::control::callback;
@ -30,20 +31,20 @@ use crate::display::style::data::DataMatch;
use crate::display::symbol::registry::SymbolRegistry;
use crate::display::symbol::Symbol;
use crate::display::symbol::SymbolId;
use crate::system;
use crate::system::gpu::data::attribute;
use crate::system::gpu::data::uniform::Uniform;
use crate::system::gpu::data::uniform::UniformScope;
use crate::system::gpu::shader::Context;
use crate::system::web;
use crate::system::web::IgnoreContextMenuHandle;
use crate::system::web::NodeInserter;
use crate::system::web::StyleSetter;
use crate::system::web::EventListenerHandle;
use crate::system::Context;
use crate::system::ContextLostHandler;
use enso_frp as frp;
use enso_frp::io::js::CurrentJsEvent;
use enso_shapely::shared;
use std::any::TypeId;
use web_sys::HtmlElement;
use web::HtmlElement;
pub trait MouseTarget: Debug + 'static {
@ -326,7 +327,7 @@ impl Mouse {
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 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 on_move = mouse_manager.on_move.add(current_js_event.make_event_handler(
f!([frp,scene_frp,position,last_position] (event:&mouse::OnMove) {
@ -414,10 +415,8 @@ pub struct Keyboard {
impl Keyboard {
pub fn new(current_event: &CurrentJsEvent) -> Self {
let logger = Logger::new("keyboard");
let frp = enso_frp::io::keyboard::Keyboard::default();
let bindings =
Rc::new(enso_frp::io::keyboard::DomBindings::new(&logger, &frp, current_event));
let bindings = Rc::new(enso_frp::io::keyboard::DomBindings::new(&frp, current_event));
Self { frp, bindings }
}
}
@ -440,12 +439,12 @@ pub struct Dom {
impl Dom {
/// Constructor.
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);
root.set_class_name("scene");
root.set_style_or_panic("height", "100vh");
root.set_style_or_panic("width", "100vw");
root.set_style_or_panic("display", "block");
root.set_style_or_warn("height", "100vh");
root.set_style_or_warn("width", "100vw");
root.set_style_or_warn("display", "block");
let root = web::dom::WithKnownShape::new(&root);
Self { root, layers }
}
@ -488,42 +487,42 @@ pub struct DomLayers {
/// Front DOM scene layer.
pub front: DomScene,
/// The WebGL scene layer.
pub canvas: web_sys::HtmlCanvasElement,
pub canvas: web::HtmlCanvasElement,
}
impl DomLayers {
/// 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);
welcome_screen.dom.set_class_name("welcome_screen");
welcome_screen.dom.set_style_or_warn("z-index", "0", logger);
dom.append_or_panic(&welcome_screen.dom);
welcome_screen.dom.set_style_or_warn("z-index", "0");
dom.append_or_warn(&welcome_screen.dom);
let back = DomScene::new(logger);
back.dom.set_class_name("back");
back.dom.set_style_or_warn("z-index", "1", logger);
dom.append_or_panic(&back.dom);
back.dom.set_style_or_warn("z-index", "1");
dom.append_or_warn(&back.dom);
let fullscreen_vis = DomScene::new(logger);
fullscreen_vis.dom.set_class_name("fullscreen_vis");
fullscreen_vis.dom.set_style_or_warn("z-index", "2", logger);
dom.append_or_panic(&fullscreen_vis.dom);
fullscreen_vis.dom.set_style_or_warn("z-index", "2");
dom.append_or_warn(&fullscreen_vis.dom);
let canvas = web::create_canvas();
canvas.set_style_or_warn("display", "block", logger);
canvas.set_style_or_warn("z-index", "3", logger);
let canvas = web::document.create_canvas_or_panic();
canvas.set_style_or_warn("display", "block");
canvas.set_style_or_warn("z-index", "3");
// These properties are set by `DomScene::new` constuctor for other layers.
// See its documentation for more info.
canvas.set_style_or_warn("position", "absolute", logger);
canvas.set_style_or_warn("height", "100vh", logger);
canvas.set_style_or_warn("width", "100vw", logger);
canvas.set_style_or_warn("pointer-events", "none", logger);
dom.append_or_panic(&canvas);
canvas.set_style_or_warn("position", "absolute");
canvas.set_style_or_warn("height", "100vh");
canvas.set_style_or_warn("width", "100vw");
canvas.set_style_or_warn("pointer-events", "none");
dom.append_or_warn(&canvas);
let front = DomScene::new(logger);
front.dom.set_class_name("front");
front.dom.set_style_or_warn("z-index", "4", logger);
dom.append_or_panic(&front.dom);
front.dom.set_style_or_warn("z-index", "4");
dom.append_or_warn(&front.dom);
Self { back, welcome_screen, fullscreen_vis, front, canvas }
}
@ -565,74 +564,107 @@ pub struct Dirty {
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 ===
// ================
/// 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)]
#[allow(missing_docs)]
pub struct Renderer {
pub logger: Logger,
dom: Dom,
context: Context,
variables: UniformScope,
pub logger: Logger,
dom: Dom,
variables: UniformScope,
pub pipeline: Rc<CloneCell<render::Pipeline>>,
pub composer: Rc<RefCell<render::Composer>>,
pub composer: Rc<RefCell<Option<render::Composer>>>,
}
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 dom = dom.clone_ref();
let context = context.clone_ref();
let variables = variables.clone_ref();
let pipeline = default();
let shape = dom.shape().device_pixels();
let width = shape.width as i32;
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));
let composer = default();
Self { logger, dom, variables, pipeline, composer }
}
context.enable(Context::BLEND);
// To learn more about the blending equations used here, please see the following articles:
// - http://www.realtimerendering.com/blog/gpus-prefer-premultiplication
// - https://www.khronos.org/opengl/wiki/Blending#Colors
context.blend_equation_separate(Context::FUNC_ADD, Context::FUNC_ADD);
context.blend_func_separate(
Context::ONE,
Context::ONE_MINUS_SRC_ALPHA,
Context::ONE,
Context::ONE_MINUS_SRC_ALPHA,
);
fn set_context(&self, context: Option<&Context>) {
let composer = context.map(|context| {
// To learn more about the blending equations used here, please see the following
// articles:
// - http://www.realtimerendering.com/blog/gpus-prefer-premultiplication
// - https://www.khronos.org/opengl/wiki/Blending#Colors
context.enable(Context::BLEND);
context.blend_equation_separate(Context::FUNC_ADD, Context::FUNC_ADD);
context.blend_func_separate(
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.
pub fn set_pipeline<P: Into<render::Pipeline>>(&self, pipeline: P) {
let pipeline = pipeline.into();
self.composer.borrow_mut().set_pipeline(&pipeline);
pub fn set_pipeline(&self, pipeline: render::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.
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();
// 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 height = shape.height.round() as i32;
self.composer.borrow_mut().resize(width, height);
(width, height)
}
/// Run the renderer.
pub fn run(&self) {
debug!(self.logger, "Running.", || {
self.composer.borrow_mut().run();
})
if let Some(composer) = &mut *self.composer.borrow_mut() {
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
/// 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.
#[derive(Clone, CloneRef, Debug)]
pub struct HardcodedLayers {
@ -801,33 +833,33 @@ impl Extensions {
#[derive(Clone, CloneRef, Debug)]
pub struct SceneData {
pub display_object: display::object::Instance,
pub dom: Dom,
pub context: Context,
pub symbols: SymbolRegistry,
pub variables: UniformScope,
pub current_js_event: CurrentJsEvent,
pub mouse: Mouse,
pub keyboard: Keyboard,
pub uniforms: Uniforms,
pub shapes: ShapeRegistry,
pub stats: Stats,
pub dirty: Dirty,
pub logger: Logger,
pub renderer: Renderer,
pub layers: HardcodedLayers,
pub style_sheet: style::Sheet,
pub bg_color_var: style::Var,
pub bg_color_change: callback::Handle,
pub frp: Frp,
extensions: Extensions,
disable_context_menu: Rc<IgnoreContextMenuHandle>,
pub display_object: display::object::Instance,
pub dom: Dom,
pub context: Rc<RefCell<Option<Context>>>,
pub context_lost_handler: Rc<RefCell<Option<ContextLostHandler>>>,
pub symbols: SymbolRegistry,
pub variables: UniformScope,
pub current_js_event: CurrentJsEvent,
pub mouse: Mouse,
pub keyboard: Keyboard,
pub uniforms: Uniforms,
pub shapes: ShapeRegistry,
pub stats: Stats,
pub dirty: Dirty,
pub logger: Logger,
pub renderer: Renderer,
pub layers: HardcodedLayers,
pub style_sheet: style::Sheet,
pub bg_color_var: style::Var,
pub bg_color_change: callback::Handle,
pub frp: Frp,
extensions: Extensions,
disable_context_menu: Rc<EventListenerHandle>,
}
impl SceneData {
/// Create new instance with the provided on-dirty callback.
pub fn new<OnMut: Fn() + Clone + 'static>(
parent_dom: &HtmlElement,
logger: Logger,
stats: &Stats,
on_mut: OnMut,
@ -835,37 +867,24 @@ impl SceneData {
debug!(logger, "Initializing.");
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);
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 variables = UniformScope::new(var_logger);
let symbols = SymbolRegistry::mk(&variables, stats, &logger, on_change);
// FIXME: This should be abstracted away and should also handle context loss when Symbol
// definition will be finally refactored in such way, that it would not require
// Scene instance to be created.
symbols.set_context(Some(&context));
let symbols_dirty = dirty_flag;
let dirty = Dirty::new(&logger, on_mut);
let symbols_dirty = &dirty.symbols;
let symbols = SymbolRegistry::mk(&variables, stats, &logger, f!(symbols_dirty.set()));
let layers = HardcodedLayers::new(&logger);
let stats = stats.clone();
let shapes = ShapeRegistry::default();
let uniforms = Uniforms::new(&variables);
let dirty = Dirty { symbols: symbols_dirty, shape: shape_dirty };
let renderer = Renderer::new(&logger, &dom, &context, &variables);
let renderer = Renderer::new(&logger, &dom, &variables);
let style_sheet = style::Sheet::new();
let current_js_event = CurrentJsEvent::new();
let frp = Frp::new(&dom.root.shape);
let mouse_logger = Logger::new_sub(&logger, "mouse");
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 network = &frp.network;
let extensions = Extensions::default();
@ -873,7 +892,7 @@ impl SceneData {
let bg_color_change = bg_color_var.on_change(f!([dom](change){
change.color().for_each(|color| {
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);
let context = default();
let context_lost_handler = default();
Self {
display_object,
dom,
context,
context_lost_handler,
symbols,
variables,
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> {
&self.dom.root.shape
}
@ -990,12 +1019,18 @@ impl SceneData {
let width = canvas.width.round() as i32;
let height = canvas.height.round() as i32;
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("height", &height.to_string()).unwrap();
self.context.viewport(0, 0, width, height);
self.dom.layers.canvas.set_attribute_or_warn("width", &width.to_string());
self.dom.layers.canvas.set_attribute_or_warn("height", &height.to_string());
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> {
let position = position / self.camera().zoom();
let position = Vector4::new(position.x, position.y, position.z, 1.0);
@ -1043,26 +1078,54 @@ pub struct Scene {
impl Scene {
pub fn new<OnMut: Fn() + Clone + 'static>(
parent_dom: &HtmlElement,
logger: impl AnyLogger,
stats: &Stats,
on_mut: OnMut,
) -> Self {
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 };
// FIXME MEMORY LEAK in all lines below:
// FIXME MEMORY LEAK:
this.no_mut_access.shapes.rc.borrow_mut().scene = Some(this.clone_ref());
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 {
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 {
fn as_ref(&self) -> &SceneData {
&self.no_mut_access
@ -1084,17 +1147,19 @@ impl Deref for Scene {
impl Scene {
pub fn update(&self, t: animation::TimeInfo) {
debug!(self.logger, "Updating.", || {
self.frp.frame_time_source.emit(t.local);
// Please note that `update_camera` is called first as it may trigger FRP events which
// may change display objects layout.
self.update_camera(self);
self.display_object.update(self);
self.layers.update();
self.update_shape();
self.update_symbols();
self.handle_mouse_events();
})
if self.context.borrow().is_some() {
debug!(self.logger, "Updating.", || {
self.frp.frame_time_source.emit(t.local);
// Please note that `update_camera` is called first as it may trigger FRP events
// which may change display objects layout.
self.update_camera(self);
self.display_object.update(self);
self.layers.update();
self.update_shape();
self.update_symbols();
self.handle_mouse_events();
})
}
}
}
@ -1109,3 +1174,46 @@ impl display::Object for Scene {
&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.
use crate::prelude::*;
use web::traits::*;
use crate::display::camera::camera2d::Projection;
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::inverse_y_translation;
use crate::display::symbol::DomSymbol;
use crate::system::gpu::data::JsBufferView;
use crate::system::web;
use crate::system::web::NodeInserter;
use crate::system::web::StyleSetter;
use web::HtmlDivElement;
#[cfg(target_arch = "wasm32")]
use crate::system::gpu::data::JsBufferView;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;
use web_sys::HtmlDivElement;
@ -22,6 +23,7 @@ use web_sys::HtmlDivElement;
// === Js Bindings ===
// ===================
#[cfg(target_arch = "wasm32")]
mod js {
use super::*;
#[wasm_bindgen(inline_js = "
@ -64,6 +66,8 @@ mod js {
}
}
#[cfg(target_arch = "wasm32")]
#[allow(unsafe_code)]
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
@ -75,6 +79,7 @@ fn setup_camera_perspective(dom: &web::JsValue, near: f32, matrix: &Matrix4<f32>
}
}
#[cfg(target_arch = "wasm32")]
#[allow(unsafe_code)]
fn setup_camera_orthographic(dom: &web::JsValue, matrix: &Matrix4<f32>) {
// 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.
pub fn new(logger: impl AnyLogger) -> Self {
let logger = Logger::new_sub(logger, "DomScene");
let dom = web::create_div();
let view_projection_dom = web::create_div();
let dom = web::document.create_div_or_panic();
let view_projection_dom = web::document.create_div_or_panic();
dom.set_class_name("dom-scene-layer");
// z-index works on positioned elements only.
dom.set_style_or_warn("position", "absolute", &logger);
dom.set_style_or_warn("top", "0px", &logger);
dom.set_style_or_warn("overflow", "hidden", &logger);
dom.set_style_or_warn("overflow", "hidden", &logger);
dom.set_style_or_warn("width", "100%", &logger);
dom.set_style_or_warn("height", "100%", &logger);
dom.set_style_or_warn("position", "absolute");
dom.set_style_or_warn("top", "0px");
dom.set_style_or_warn("overflow", "hidden");
dom.set_style_or_warn("overflow", "hidden");
dom.set_style_or_warn("width", "100%");
dom.set_style_or_warn("height", "100%");
// 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.
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_style_or_warn("width", "100%", &logger);
view_projection_dom.set_style_or_warn("height", "100%", &logger);
view_projection_dom.set_style_or_warn("transform-style", "preserve-3d", &logger);
view_projection_dom.set_style_or_warn("width", "100%");
view_projection_dom.set_style_or_warn("height", "100%");
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 = Rc::new(data);
@ -183,13 +200,13 @@ impl DomScene {
/// Sets the z-index of this DOM element.
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
/// the element normally. A value of 1.0 will make the element completely gray.
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.
@ -197,11 +214,11 @@ impl DomScene {
let dom = object.dom();
let data = &self.data;
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_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::*;
#[wasm_bindgen(module = "/src/display/shape/primitive/glsl/overload.js")]
extern "C" {
/// Returns GLSL code which redirects mangled function names to their original primitive
/// definitions.
#[allow(unsafe_code)]
pub fn builtin_redirections() -> String;
mod js {
use super::*;
#[wasm_bindgen(module = "/src/display/shape/primitive/glsl/overload.js")]
extern "C" {
/// Returns GLSL code which redirects mangled function names to their original primitive
/// definitions.
#[allow(unsafe_code)]
pub fn builtin_redirections() -> String;
/// Mangles the provided GLSL code to allow primitive definitions overloading.
#[allow(unsafe_code)]
pub fn allow_overloading(s: &str) -> String;
/// Mangles the provided GLSL code to allow primitive definitions overloading.
#[allow(unsafe_code)]
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