mirror of
https://github.com/enso-org/enso.git
synced 2024-08-17 00:40:34 +03:00
EnsoGL context abstraction (#3293)
This commit is contained in:
parent
d3846578cc
commit
f4d236fcd4
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1461,6 +1461,7 @@ dependencies = [
|
||||
name = "ensogl-example-sprite-system"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"enso-web",
|
||||
"ensogl-core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -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/*"]
|
||||
|
||||
|
@ -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"] }
|
||||
|
@ -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" }
|
||||
|
@ -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"
|
||||
] }
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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()}.");
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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"
|
||||
] }
|
||||
|
@ -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"] }
|
||||
|
@ -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;
|
||||
|
@ -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"] }
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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)",
|
||||
|
@ -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)));
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
] }
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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.
|
@ -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"
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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))
|
||||
})
|
||||
})
|
@ -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)
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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 }
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 }
|
||||
|
@ -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>();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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" }
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -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"
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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::*;
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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 = ""))]
|
||||
|
@ -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, ¤t_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(¤t_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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user