Allowing EnsoGL mouse to interact with more than 4096 sprites at the same time. (#3351)

This commit is contained in:
Wojciech Daniło 2022-03-29 04:15:08 +02:00 committed by GitHub
parent 20be5516a5
commit 546c333269
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 1196 additions and 549 deletions

12
Cargo.lock generated
View File

@ -1354,6 +1354,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "ensogl-example-custom-shape-system"
version = "0.1.0"
dependencies = [
"enso-frp",
"enso-profiler",
"ensogl-core",
"wasm-bindgen",
]
[[package]]
name = "ensogl-example-dom-symbols"
version = "0.1.0"
@ -1491,6 +1501,7 @@ dependencies = [
name = "ensogl-example-shape-system"
version = "0.1.0"
dependencies = [
"enso-profiler",
"ensogl-core",
"wasm-bindgen",
]
@ -1541,6 +1552,7 @@ version = "0.1.0"
dependencies = [
"ensogl-example-animation",
"ensogl-example-complex-shape-system",
"ensogl-example-custom-shape-system",
"ensogl-example-dom-symbols",
"ensogl-example-drop-manager",
"ensogl-example-easing-animator",

View File

@ -265,9 +265,10 @@ impl ProjectName {
// === Mouse IO ===
mouse_down <- model.view.events.mouse_down.constant(());
frp.source.is_hovered <+ bool(&model.view.events.mouse_out,
&model.view.events.mouse_over);
frp.source.mouse_down <+ model.view.events.mouse_down;
frp.source.mouse_down <+ model.view.events.mouse_down.constant(());
not_selected <- frp.output.selected.map(|selected| !selected);
mouse_over_if_not_selected <- model.view.events.mouse_over.gate(&not_selected);
@ -280,7 +281,7 @@ impl ProjectName {
);
on_deselect <- not_selected.gate(&not_selected).constant(());
edit_click <- model.view.events.mouse_down.gate(&frp.ide_text_edit_mode);
edit_click <- mouse_down.gate(&frp.ide_text_edit_mode);
start_editing <- any(edit_click,frp.input.start_editing);
eval_ start_editing ({
text.set_focus(true);

View File

@ -12,7 +12,7 @@ use ensogl::application::Application;
use ensogl::data::color;
use ensogl::display;
use ensogl::display::scene::Scene;
use ensogl::gui::component::ShapeViewEvents;
use ensogl::gui::component::PointerTarget;
use ensogl_hardcoded_theme as theme;
use nalgebra::Rotation2;
@ -74,7 +74,7 @@ trait EdgeShape: display::Object {
fn id(&self) -> display::object::Id {
self.display_object().id()
}
fn events(&self) -> &ShapeViewEvents;
fn events(&self) -> &PointerTarget;
fn set_color(&self, color: color::Rgba);
fn set_color_focus(&self, color: color::Rgba);
@ -172,8 +172,8 @@ trait AnyEdgeShape {
/// Return references to all `EdgeShape`s in this `AnyEdgeShape`.
fn shapes(&self) -> Vec<&dyn EdgeShape>;
/// Connect the given `ShapeViewEventsProxy` to the mouse events of all sub-shapes.
fn register_proxy_frp(&self, network: &frp::Network, frp: &ShapeViewEventsProxy) {
/// Connect the given `PointerTargetProxy` to the mouse events of all sub-shapes.
fn register_proxy_frp(&self, network: &frp::Network, frp: &PointerTargetProxy) {
for shape in &self.shapes() {
let event = shape.events();
let id = shape.id();
@ -374,7 +374,7 @@ macro_rules! define_corner_start {
self.focus_split_angle.set(angle);
}
fn events(&self) -> &ShapeViewEvents {
fn events(&self) -> &PointerTarget {
&self.events
}
@ -469,7 +469,7 @@ macro_rules! define_corner_end {
self.focus_split_angle.set(angle);
}
fn events(&self) -> &ShapeViewEvents {
fn events(&self) -> &PointerTarget {
&self.events
}
@ -549,7 +549,7 @@ macro_rules! define_line {
self.focus_split_angle.set(angle);
}
fn events(&self) -> &ShapeViewEvents {
fn events(&self) -> &PointerTarget {
&self.events
}
@ -621,7 +621,7 @@ macro_rules! define_arrow { () => {
self.focus_split_angle.set(angle);
}
fn events(&self) -> &ShapeViewEvents {
fn events(&self) -> &PointerTarget {
&self.events
}
@ -752,7 +752,7 @@ macro_rules! define_components {
pub struct $name {
pub logger : Logger,
pub display_object : display::object::Instance,
pub shape_view_events : Rc<Vec<ShapeViewEvents>>,
pub shape_view_events : Rc<Vec<PointerTarget>>,
shape_type_map : Rc<HashMap<display::object::Id,ShapeRole>>,
$(pub $field : $field_type),*
}
@ -764,7 +764,7 @@ macro_rules! define_components {
let display_object = display::object::Instance::new(&logger);
$(let $field = <$field_type>::new(Logger::new_sub(&logger,stringify!($field)));)*
$(display_object.add_child(&$field);)*
let mut shape_view_events:Vec<ShapeViewEvents> = Vec::default();
let mut shape_view_events:Vec<PointerTarget> = Vec::default();
$(shape_view_events.push($field.events.clone_ref());)*
let shape_view_events = Rc::new(shape_view_events);
@ -1021,7 +1021,7 @@ impl SemanticSplit {
/// emit events via th internal `on_mouse_down`/`on_mouse_over`/`on_mouse_out` sources.
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct ShapeViewEventsProxy {
pub struct PointerTargetProxy {
pub mouse_down: frp::Stream,
pub mouse_over: frp::Stream,
pub mouse_out: frp::Stream,
@ -1032,7 +1032,7 @@ pub struct ShapeViewEventsProxy {
}
#[allow(missing_docs)]
impl ShapeViewEventsProxy {
impl PointerTargetProxy {
pub fn new(network: &frp::Network) -> Self {
frp::extend! { network
on_mouse_over <- source();
@ -1063,7 +1063,7 @@ pub struct Frp {
pub set_color: frp::Source<color::Lcha>,
pub hover_position: frp::Source<Option<Vector2<f32>>>,
pub shape_events: ShapeViewEventsProxy,
pub shape_events: PointerTargetProxy,
}
impl Frp {
@ -1080,7 +1080,7 @@ impl Frp {
def set_disabled = source();
def set_color = source();
}
let shape_events = ShapeViewEventsProxy::new(network);
let shape_events = PointerTargetProxy::new(network);
Self {
source_width,
source_height,

View File

@ -696,7 +696,7 @@ impl Node {
// === Background Press ===
out.background_press <+ model.drag_area.events.mouse_down;
out.background_press <+ model.drag_area.events.mouse_down.constant(());
out.background_press <+ model.input.on_background_press;

View File

@ -9,12 +9,15 @@ use ensogl::prelude::*;
use crate::GraphEditorModelWithNetwork;
use crate::NodeId;
use enso_frp as frp;
use ensogl::animation::easing::EndStatus::Normal;
use ensogl::display::Scene;
use ensogl::Animation;
use ensogl::Easing;
/// Describes the "speed" of growth/shrink animation.
///
/// To determine the duration of the blending animation, we divide the length of the camera path by

View File

@ -635,7 +635,7 @@ impl Area {
let mouse_over_raw = port_shape.hover.events.mouse_over.clone_ref();
let mouse_out = port_shape.hover.events.mouse_out.clone_ref();
let mouse_down_raw = port_shape.hover.events.mouse_down.clone_ref();
mouse_down_raw <- port_shape.hover.events.mouse_down.constant(());
// === Body Hover ===

View File

@ -403,7 +403,7 @@ impl PortShapeView {
set_padding_right (this,t:f32) { this.padding_right.set(t) }
}
fn events(&self) -> &component::ShapeViewEvents {
fn events(&self) -> &component::PointerTarget {
match self {
Self::Single(t) => &t.events,
Self::Multi(t) => &t.events,
@ -525,7 +525,7 @@ impl Model {
// === Mouse Event Handling ===
frp.source.on_hover <+ bool(&events.mouse_out,&events.mouse_over);
frp.source.on_press <+ events.mouse_down;
frp.source.on_press <+ events.mouse_down.constant(());
// === Opacity ===

View File

@ -443,7 +443,7 @@ impl ContainerModel {
}
/// Check if given mouse-event-target means this visualization.
fn is_this_target(&self, target: scene::PointerTarget) -> bool {
fn is_this_target(&self, target: scene::PointerTargetId) -> bool {
self.view.overlay.is_this_target(target)
}

View File

@ -394,15 +394,16 @@ impl ActionBar {
frp.source.visualisation_selection <+ visualization_chooser.chosen_entry;
let reset_position_icon = &model.icons.reset_position_icon.events;
frp.source.on_container_reset_position <+ reset_position_icon.mouse_down;
reset_position_icon_down <- reset_position_icon.mouse_down.constant(());
frp.source.on_container_reset_position <+ reset_position_icon_down;
let drag_icon = &model.icons.drag_icon.events;
let start_dragging = drag_icon.mouse_down.clone_ref();
start_dragging <- drag_icon.mouse_down.constant(());
end_dragging <- mouse.up.gate(&frp.source.container_drag_state);
should_drag <- bool(&end_dragging,&start_dragging);
frp.source.container_drag_state <+ should_drag;
show_reset_icon <- bool(&reset_position_icon.mouse_down,&start_dragging);
show_reset_icon <- bool(&reset_position_icon_down,&start_dragging);
eval show_reset_icon((visibility) model.icons.set_reset_icon_visibility(*visibility));
}
self

View File

@ -2623,33 +2623,14 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// === Selection Target Redirection ===
frp::extend! { network
mouse_down_target <- mouse.down_primary.map(f_!(model.scene().mouse.target.get()));
let scene = model.scene();
mouse_up_target <- mouse.up_primary.map(f_!(model.scene().mouse.target.get()));
background_up <= mouse_up_target.map(
|t| (t==&display::scene::PointerTarget::Background).as_some(())
|t| (t==&display::scene::PointerTargetId::Background).as_some(())
);
eval mouse_down_target([touch,model](target) {
match target {
display::scene::PointerTarget::Background => touch.background.down.emit(()),
display::scene::PointerTarget::Symbol {..} => {
if let Some(target) = model.scene().shapes.get_mouse_target(*target) {
target.mouse_down().emit(());
}
}
}
});
eval mouse_up_target([model](target) {
match target {
display::scene::PointerTarget::Background => {} // touch.background.up.emit(()),
display::scene::PointerTarget::Symbol {..} => {
if let Some(target) = model.scene().shapes.get_mouse_target(*target) {
target.mouse_up().emit(());
}
}
}
});
eval_ scene.background.mouse_down (touch.background.down.emit(()));
}

View File

@ -195,8 +195,8 @@ pub mod right_overflow {
use enso_frp::Network;
use ensogl_core::frp::io::Mouse;
use ensogl_core::gui::component::PointerTarget;
use ensogl_core::gui::component::ShapeView;
use ensogl_core::gui::component::ShapeViewEvents;
pub use super::frp::*;
pub use super::model::*;
@ -208,7 +208,7 @@ use ensogl_core::display::Scene;
/// Dragging is ended by a mouse up.
pub fn shape_is_dragged(
network: &Network,
shape: &ShapeViewEvents,
shape: &PointerTarget,
mouse: &Mouse,
) -> enso_frp::Stream<bool> {
enso_frp::extend! { network
@ -259,7 +259,7 @@ mod tests {
fn test_shape_is_dragged() {
let network = enso_frp::Network::new("TestNetwork");
let mouse = enso_frp::io::Mouse::default();
let shape = ShapeViewEvents::default();
let shape = PointerTarget::default();
let is_dragged = shape_is_dragged(&network, &shape, &mouse);
let _watch = is_dragged.register_watch();

View File

@ -230,7 +230,7 @@ impl<Shape: ColorableShape + 'static> ToggleButton<Shape> {
// === State ===
toggle <- any(frp.toggle,icon.mouse_down);
toggle <- any_(frp.toggle,icon.mouse_down);
frp.source.state <+ frp.state.not().sample(&toggle);
frp.source.state <+ frp.set_state;

View File

@ -185,15 +185,16 @@ macro_rules! gen_stats {
self.[<set _ $field>](value);
}
// FIXME: saturating_add is proper solution, but even without it it should not crash, but it does. To be investigated.
emit_if_integer!($field_type,
/// Increments field's value.
pub fn [<inc _ $field>](&self) {
self.[<mod _ $field>](|t| t + 1);
self.[<mod _ $field>](|t| t.saturating_add(1));
}
/// Decrements field's value.
pub fn [<dec _ $field>](&self) {
self.[<mod _ $field>](|t| t - 1);
self.[<mod _ $field>](|t| t.saturating_sub(1));
}
);

View File

@ -1188,6 +1188,33 @@ impl<Host> Object<Host> for Any<Host> {
// =========================
// === UnsetParentOnDrop ===
// =========================
/// Wrapper that unsets parent of a display object when dropped. Please note that [`Instance`]
/// implements [`CloneRef`], so it can still be alive even if this struct is dropped.
#[derive(Debug, NoCloneBecauseOfCustomDrop)]
pub struct UnsetParentOnDrop {
instance: Instance,
}
impl UnsetParentOnDrop {
/// Constructor.
pub fn new(instance: impl Into<Instance>) -> Self {
let instance = instance.into();
Self { instance }
}
}
impl Drop for UnsetParentOnDrop {
fn drop(&mut self) {
self.instance.unset_parent()
}
}
// =============
// === Tests ===
// =============

View File

@ -22,9 +22,7 @@ use crate::display::style;
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::web;
@ -39,6 +37,7 @@ use std::any::TypeId;
use web::HtmlElement;
// ==============
// === Export ===
// ==============
@ -47,18 +46,13 @@ use web::HtmlElement;
pub mod dom;
#[warn(missing_docs)]
pub mod layer;
#[warn(missing_docs)]
pub mod pointer_target;
pub use crate::system::web::dom::Shape;
pub use layer::Layer;
pub trait MouseTarget: Debug + 'static {
fn mouse_down(&self) -> &frp::Source;
fn mouse_up(&self) -> &frp::Source;
fn mouse_over(&self) -> &frp::Source;
fn mouse_out(&self) -> &frp::Source;
}
pub use pointer_target::PointerTarget;
pub use pointer_target::PointerTargetId;
@ -67,17 +61,24 @@ pub trait MouseTarget: Debug + 'static {
// =====================
shared! { ShapeRegistry
#[derive(Debug,Default)]
#[derive(Debug)]
pub struct ShapeRegistryData {
// FIXME[WD]: The only valid field here is the `mouse_target_map`. The rest should be removed
// after proper implementation of text depth sorting, which is the only component
// using the obsolete fields now.
scene : Option<Scene>,
shape_system_map : HashMap<TypeId,Box<dyn Any>>,
mouse_target_map : HashMap<(SymbolId,attribute::InstanceIndex),Rc<dyn MouseTarget>>,
mouse_target_map : HashMap<PointerTargetId, PointerTarget>,
}
impl {
fn new(background: &PointerTarget) -> Self {
let scene = default();
let shape_system_map = default();
let mouse_target_map = default();
Self {scene, shape_system_map, mouse_target_map} . init(background)
}
fn get<T:ShapeSystemInstance>(&self) -> Option<T> {
let id = TypeId::of::<T>();
self.shape_system_map.get(&id).and_then(|t| t.downcast_ref::<T>()).map(|t| t.clone_ref())
@ -105,198 +106,30 @@ impl {
system.new_instance()
}
pub fn insert_mouse_target<T:MouseTarget>
(&mut self, symbol_id:SymbolId, instance_id:attribute::InstanceIndex, target:T) {
let target = Rc::new(target);
self.mouse_target_map.insert((symbol_id,instance_id),target);
pub fn insert_mouse_target
(&mut self, id:impl Into<PointerTargetId>, target:impl Into<PointerTarget>) {
self.mouse_target_map.insert(id.into(),target.into());
}
pub fn remove_mouse_target
(&mut self, symbol_id:SymbolId, instance_id:attribute::InstanceIndex) {
self.mouse_target_map.remove(&(symbol_id,instance_id));
(&mut self, id:impl Into<PointerTargetId>) {
self.mouse_target_map.remove(&id.into());
}
pub fn get_mouse_target(&mut self, target:PointerTarget) -> Option<Rc<dyn MouseTarget>> {
match target {
PointerTarget::Background => None,
PointerTarget::Symbol {symbol_id,instance_id} => {
self.mouse_target_map.get(&(symbol_id,instance_id)).cloned()
}
pub fn get_mouse_target(&self, target:PointerTargetId) -> Option<PointerTarget> {
self.mouse_target_map.get(&target).cloned()
}
pub fn with_mouse_target<T>
(&self, target:PointerTargetId, f: impl FnOnce(&PointerTarget) -> T) -> Option<T> {
self.mouse_target_map.get(&target).as_ref().map(|t| f(t))
}
}}
// ==============
// === Target ===
// ==============
/// Result of a Decoding operation in the Target.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum DecodingResult {
/// Values had to be truncated.
Truncated(u8, u8, u8),
/// Values have been encoded successfully.
Ok(u8, u8, u8),
}
/// Mouse target. Contains a path to an object pointed by mouse.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum PointerTarget {
Background,
Symbol { symbol_id: SymbolId, instance_id: attribute::InstanceIndex },
}
impl PointerTarget {
/// Encode two u32 values into three u8 values.
///
/// This is the same encoding that is used in the `fragment_runner`. This encoding is lossy and
/// can only encode values up to 4096 (2^12) each.
///
/// We use 12 bits from each value and pack them into the 3 output bytes like described in the
/// following diagram.
///
/// ```text
/// Input
///
/// value1 (v1) as bytes value2 (v2) as bytes
/// +-----+-----+-----+-----+ +-----+-----+-----+-----+
/// | | | | | | | | | |
/// +-----+-----+-----+-----+ +-----+-----+-----+-----+
/// 32 24 16 8 0 32 24 16 8 0 <- Bit index
///
///
/// Output
///
/// byte1 byte2 byte3
/// +-----------+ +----------------------+ +------------+
/// | v ]12..4] | | v1 ]4..0] v2 ]4..0] | | v2 ]12..4] |
/// +-----------+ +----------------------+ +------------+
///
/// Ranges use mathematical notation for inclusion/exclusion.
/// ```
fn encode(value1: u32, value2: u32) -> DecodingResult {
let chunk1 = (value1 >> 4u32) & 0x00FFu32;
let chunk2 = (value1 & 0x000Fu32) << 4u32;
let chunk2 = chunk2 | ((value2 & 0x0F00u32) >> 8u32);
let chunk3 = value2 & 0x00FFu32;
if value1 > 2u32.pow(12) || value2 > 2u32.pow(12) {
DecodingResult::Truncated(chunk1 as u8, chunk2 as u8, chunk3 as u8)
} else {
DecodingResult::Ok(chunk1 as u8, chunk2 as u8, chunk3 as u8)
}
}
/// Decode the symbol_id and instance_id that was encoded in the `fragment_runner`.
///
/// See the `encode` method for more information on the encoding.
fn decode(chunk1: u32, chunk2: u32, chunk3: u32) -> (u32, u32) {
let value1 = (chunk1 << 4) + (chunk2 >> 4);
let value2 = chunk3 + ((chunk2 & 0x000F) << 8);
(value1, value2)
}
fn to_internal(self, logger: &Logger) -> Vector4<u32> {
match self {
Self::Background => Vector4::new(0, 0, 0, 0),
Self::Symbol { symbol_id, instance_id } => {
match Self::encode(*symbol_id, (*instance_id) as u32) {
DecodingResult::Truncated(pack0, pack1, pack2) => {
warning!(
logger,
"Target values too big to encode: \
({symbol_id},{instance_id})."
);
Vector4::new(pack0.into(), pack1.into(), pack2.into(), 1)
}
DecodingResult::Ok(pack0, pack1, pack2) =>
Vector4::new(pack0.into(), pack1.into(), pack2.into(), 1),
}
}
}
}
fn from_internal(v: Vector4<u32>) -> Self {
if v.w == 0 {
Self::Background
} else if v.w == 255 {
let decoded = Self::decode(v.x, v.y, v.z);
let symbol_id = SymbolId::new(decoded.0);
let instance_id = attribute::InstanceIndex::new(decoded.1 as usize);
Self::Symbol { symbol_id, instance_id }
} else {
panic!("Wrong internal format alpha for mouse target.")
}
}
pub fn is_background(self) -> bool {
self == Self::Background
}
pub fn is_symbol(self) -> bool {
!self.is_background()
}
}
impl Default for PointerTarget {
fn default() -> Self {
Self::Background
}
}
// === Target Tests ===
#[cfg(test)]
mod target_tests {
use super::*;
/// Asserts that decoding encoded the given values returns the correct initial values again.
/// That means that `decode(encode(value1,value2)) == (value1,value2)`.
fn assert_valid_roundtrip(value1: u32, value2: u32) {
let pack = PointerTarget::encode(value1, value2);
match pack {
DecodingResult::Truncated { .. } => {
panic!("Values got truncated. This is an invalid test case: {}, {}", value1, value1)
}
DecodingResult::Ok(pack0, pack1, pack2) => {
let unpack = PointerTarget::decode(pack0.into(), pack1.into(), pack2.into());
assert_eq!(unpack.0, value1);
assert_eq!(unpack.1, value2);
}
}
}
#[test]
fn test_roundtrip_coding() {
assert_valid_roundtrip(0, 0);
assert_valid_roundtrip(0, 5);
assert_valid_roundtrip(512, 0);
assert_valid_roundtrip(1024, 64);
assert_valid_roundtrip(1024, 999);
}
#[test]
fn test_encoding() {
let pack = PointerTarget::encode(0, 0);
assert_eq!(pack, DecodingResult::Ok(0, 0, 0));
let pack = PointerTarget::encode(3, 7);
assert_eq!(pack, DecodingResult::Ok(0, 48, 7));
let pack = PointerTarget::encode(3, 256);
assert_eq!(pack, DecodingResult::Ok(0, 49, 0));
let pack = PointerTarget::encode(255, 356);
assert_eq!(pack, DecodingResult::Ok(15, 241, 100));
let pack = PointerTarget::encode(256, 356);
assert_eq!(pack, DecodingResult::Ok(16, 1, 100));
let pack = PointerTarget::encode(31256, 0);
assert_eq!(pack, DecodingResult::Truncated(161, 128, 0));
impl ShapeRegistryData {
fn init(mut self, background: &PointerTarget) -> Self {
self.mouse_target_map.insert(PointerTargetId::Background, background.clone_ref());
self
}
}
@ -311,8 +144,8 @@ pub struct Mouse {
pub mouse_manager: MouseManager,
pub last_position: Rc<Cell<Vector2<i32>>>,
pub position: Uniform<Vector2<i32>>,
pub hover_ids: Uniform<Vector4<u32>>,
pub target: Rc<Cell<PointerTarget>>,
pub hover_rgba: Uniform<Vector4<u32>>,
pub target: Rc<Cell<PointerTargetId>>,
pub handles: Rc<[callback::Handle; 4]>,
pub frp: enso_frp::io::Mouse,
pub scene_frp: Frp,
@ -328,10 +161,10 @@ impl Mouse {
logger: Logger,
) -> Self {
let scene_frp = scene_frp.clone_ref();
let target = PointerTarget::default();
let target = PointerTargetId::default();
let last_position = Rc::new(Cell::new(Vector2::new(0, 0)));
let position = variables.add_or_panic("mouse_position", Vector2::new(0, 0));
let hover_ids = variables.add_or_panic("mouse_hover_ids", target.to_internal(&logger));
let position = variables.add_or_panic("mouse_position", Vector2(0, 0));
let hover_rgba = variables.add_or_panic("mouse_hover_ids", Vector4(0, 0, 0, 0));
let target = Rc::new(Cell::new(target));
let mouse_manager = MouseManager::new_separated(&root.clone_ref().into(), &web::window);
let frp = frp::io::Mouse::new();
@ -370,7 +203,7 @@ impl Mouse {
mouse_manager,
last_position,
position,
hover_ids,
hover_rgba,
target,
handles,
frp,
@ -382,12 +215,12 @@ impl Mouse {
/// Re-emits FRP mouse changed position event with the last mouse position value.
///
/// The immediate question that appears is why it is even needed. The reason is tightly coupled
/// with how the rendering engine works and it is important to understand it properly. When
/// with how the rendering engine works, and it is important to understand it properly. When
/// moving a mouse the following events happen:
/// - `MouseManager` gets notification and fires callbacks.
/// - Callback above is run. The value of `screen_position` uniform changes and FRP events are
/// emitted.
/// - FRP events propagate trough the whole system.
/// - FRP events propagate through the whole system.
/// - The rendering engine renders a frame and waits for the pixel read pass to report symbol ID
/// under the cursor. This is normally done the next frame but sometimes could take even few
/// frames.
@ -872,6 +705,7 @@ pub struct SceneData {
pub mouse: Mouse,
pub keyboard: Keyboard,
pub uniforms: Uniforms,
pub background: PointerTarget,
pub shapes: ShapeRegistry,
pub stats: Stats,
pub dirty: Dirty,
@ -905,7 +739,8 @@ impl SceneData {
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 background = PointerTarget::new();
let shapes = ShapeRegistry::new(&background);
let uniforms = Uniforms::new(&variables);
let renderer = Renderer::new(&logger, &dom, &variables);
let style_sheet = style::Sheet::new();
@ -945,6 +780,7 @@ impl SceneData {
keyboard,
uniforms,
shapes,
background,
stats,
dirty,
logger,
@ -957,6 +793,12 @@ impl SceneData {
extensions,
disable_context_menu,
}
.init()
}
fn init(self) -> Self {
self.init_mouse_down_and_up_events();
self
}
pub fn set_context(&self, context: Option<&Context>) {
@ -983,17 +825,6 @@ impl SceneData {
&self.symbols
}
fn handle_mouse_events(&self) {
let new_target = PointerTarget::from_internal(self.mouse.hover_ids.get());
let current_target = self.mouse.target.get();
if new_target != current_target {
self.mouse.target.set(new_target);
self.shapes.get_mouse_target(current_target).for_each(|t| t.mouse_out().emit(()));
self.shapes.get_mouse_target(new_target).for_each(|t| t.mouse_over().emit(()));
self.mouse.re_emit_position_event(); // See docs to learn why.
}
}
fn update_shape(&self) {
if self.dirty.shape.check_all() {
let screen = self.dom.shape();
@ -1094,6 +925,52 @@ impl SceneData {
}
}
// === Mouse ===
impl SceneData {
/// Init handling of mouse up and down events. It is also responsible for discovering of the
/// mouse release events. To learn more see the documentation of [`PointerTarget`].
fn init_mouse_down_and_up_events(&self) {
let network = &self.frp.network;
let shapes = &self.shapes;
let target = &self.mouse.target;
let pressed: Rc<RefCell<HashMap<mouse::Button, PointerTargetId>>> = default();
frp::extend! { network
eval self.mouse.frp.down ([shapes,target,pressed](button) {
let current_target = target.get();
pressed.borrow_mut().insert(*button,current_target);
shapes.with_mouse_target(current_target, |t| t.mouse_down.emit(button));
});
eval self.mouse.frp.up ([shapes,target,pressed](button) {
let current_target = target.get();
if let Some(last_target) = pressed.borrow_mut().remove(button) {
shapes.with_mouse_target(last_target, |t| t.mouse_release.emit(button));
}
shapes.with_mouse_target(current_target, |t| t.mouse_up.emit(button));
});
}
}
/// Discover what object the mouse pointer is on.
fn handle_mouse_over_and_out_events(&self) {
let opt_new_target = PointerTargetId::decode_from_rgba(self.mouse.hover_rgba.get());
let new_target = opt_new_target.unwrap_or_else(|err| {
error!(self.logger, "{err}");
default()
});
let current_target = self.mouse.target.get();
if new_target != current_target {
self.mouse.target.set(new_target);
self.shapes.get_mouse_target(current_target).for_each(|t| t.mouse_out.emit(()));
self.shapes.get_mouse_target(new_target).for_each(|t| t.mouse_over.emit(()));
self.mouse.re_emit_position_event(); // See docs to learn why.
}
}
}
impl display::Object for SceneData {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
@ -1193,7 +1070,7 @@ impl Scene {
self.layers.update();
self.update_shape();
self.update_symbols();
self.handle_mouse_events();
self.handle_mouse_over_and_out_events();
})
}
}

View File

@ -13,8 +13,8 @@ use crate::display::shape::system::DynShapeSystemOf;
use crate::display::shape::system::KnownShapeSystemId;
use crate::display::shape::system::ShapeSystemId;
use crate::display::shape::ShapeSystemInstance;
use crate::display::symbol;
use crate::display::symbol::SymbolId;
use crate::system::gpu::data::attribute;
use enso_data_structures::dependency_graph::DependencyGraph;
use enso_shapely::shared;
@ -199,10 +199,10 @@ impl Layer {
/// Instantiate the provided [`DynamicShape`].
pub fn instantiate<T>(&self, scene: &Scene, shape: &T) -> LayerDynamicShapeInstance
where T: display::shape::system::DynamicShape {
let (shape_system_info, symbol_id, instance_id) =
let (shape_system_info, symbol_id, global_instance_id) =
self.shape_system_registry.instantiate(scene, shape);
self.add_shape(shape_system_info, symbol_id);
LayerDynamicShapeInstance::new(self, symbol_id, instance_id)
LayerDynamicShapeInstance::new(self, global_instance_id)
}
/// Iterate over all layers and sublayers of this layer hierarchically. Parent layers will be
@ -791,15 +791,14 @@ impl std::borrow::Borrow<LayerModel> for Layer {
#[allow(missing_docs)]
pub struct LayerDynamicShapeInstance {
pub layer: WeakLayer,
pub symbol_id: SymbolId,
pub instance_id: attribute::InstanceIndex,
pub global_instance_id: symbol::GlobalInstanceId,
}
impl LayerDynamicShapeInstance {
/// Constructor.
pub fn new(layer: &Layer, symbol_id: SymbolId, instance_id: attribute::InstanceIndex) -> Self {
pub fn new(layer: &Layer, global_instance_id: symbol::GlobalInstanceId) -> Self {
let layer = layer.downgrade();
Self { layer, symbol_id, instance_id }
Self { layer, global_instance_id }
}
}
@ -962,19 +961,19 @@ impl {
/// Instantiate the provided [`DynamicShape`].
pub fn instantiate<T>
(&mut self, scene:&Scene, shape:&T) -> (ShapeSystemInfo,SymbolId,attribute::InstanceIndex)
(&mut self, scene:&Scene, shape:&T) -> (ShapeSystemInfo, SymbolId, symbol::GlobalInstanceId)
where T : display::shape::system::DynamicShape {
self.with_get_or_register_mut::<DynShapeSystemOf<T>,_,_>(scene,|entry| {
let system = entry.shape_system;
let system_id = DynShapeSystemOf::<T>::id();
let instance_id = system.instantiate(shape);
let global_instance_id = system.instantiate(shape);
let symbol_id = system.shape_system().sprite_system.symbol.id;
let above = DynShapeSystemOf::<T>::above();
let below = DynShapeSystemOf::<T>::below();
let ordering = ShapeSystemStaticDepthOrdering {above,below};
let shape_system_info = ShapeSystemInfo::new(system_id,ordering);
*entry.instance_count += 1;
(shape_system_info,symbol_id,instance_id)
(shape_system_info, symbol_id, global_instance_id)
})
}

View File

@ -0,0 +1,168 @@
//! Abstractions for objects that can interact with the pointer (in most cases this is a mouse).
use crate::prelude::*;
use crate::control::io::mouse;
use crate::display::symbol;
use enso_frp as frp;
// =================
// === Constants ===
// =================
const ID_ENCODING_OVERFLOW_ERR: u32 =
include!("../shape/primitive/glsl/error_codes/id_encoding_overflow.txt");
// =====================
// === PointerTarget ===
// =====================
/// Abstraction for objects that can interact with a mouse.
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct PointerTarget {
network: frp::Network,
/// Mouse button was pressed while the pointer was hovering this object.
pub mouse_down: frp::Source<mouse::Button>,
/// Mouse button was released while the pointer was hovering this object.
pub mouse_up: frp::Source<mouse::Button>,
/// Mouse button that was earlier pressed on this object was just released. The mouse pointer
/// does not have to hover this object anymore.
pub mouse_release: frp::Source<mouse::Button>,
/// Mouse pointer entered the object shape.
pub mouse_over: frp::Source,
/// Mouse pointer exited the object shape.
pub mouse_out: frp::Source,
/// The mouse target was dropped.
pub on_drop: frp::Source,
}
impl PointerTarget {
/// Constructor.
pub fn new() -> Self {
frp::new_network! { network
on_drop <- source_();
mouse_down <- source();
mouse_up <- source();
mouse_release <- source();
mouse_over <- source_();
mouse_out <- source_();
is_mouse_over <- bool(&mouse_out,&mouse_over);
out_on_drop <- on_drop.gate(&is_mouse_over);
eval_ out_on_drop (mouse_out.emit(()));
}
Self { network, mouse_down, mouse_up, mouse_release, mouse_over, mouse_out, on_drop }
}
}
impl Default for PointerTarget {
fn default() -> Self {
Self::new()
}
}
// =======================
// === PointerTargetId ===
// =======================
/// Pointer target ID, a unique ID for an object pointed by the mouse.
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
#[allow(missing_docs)]
pub enum PointerTargetId {
Background,
Symbol { id: symbol::GlobalInstanceId },
}
impl PointerTargetId {
/// Decode the [`PointerTargetId`] from an RGBA value. If alpha is set to 0, the result will be
/// background. In case alpha is 255, the result will be decoded based on the first 3 bytes,
/// which allows for storing up to 16 581 375 unique IDs.
///
/// Please see the [`fragment_runner.glsl`] file to see the encoding implementation and learn
/// more about the possible overflow behavior.
pub fn decode_from_rgba(v: Vector4<u32>) -> Result<Self, DecodeError> {
let alpha = v.w;
match alpha {
0 => Ok(Self::Background),
255 => {
let raw_id = Self::decode_raw(v.x, v.y, v.z);
let id = symbol::GlobalInstanceId::new(raw_id);
Ok(Self::Symbol { id })
}
_ => {
let err = if alpha == ID_ENCODING_OVERFLOW_ERR {
DecodeError::Overflow
} else {
DecodeError::WrongAlpha(alpha)
};
Err(err)
}
}
}
fn decode_raw(r: u32, g: u32, b: u32) -> u32 {
(b << 16) + (g << 8) + r
}
/// Check whether this id points to the background.
pub fn is_background(self) -> bool {
self == Self::Background
}
/// Check whether this id points to a symbol.
pub fn is_symbol(self) -> bool {
!self.is_background()
}
}
impl Default for PointerTargetId {
fn default() -> Self {
Self::Background
}
}
impl From<symbol::GlobalInstanceId> for PointerTargetId {
fn from(id: symbol::GlobalInstanceId) -> Self {
Self::Symbol { id }
}
}
impl From<&symbol::GlobalInstanceId> for PointerTargetId {
fn from(id: &symbol::GlobalInstanceId) -> Self {
Self::from(*id)
}
}
// === Errors ===
/// [`PointerTargetId`] decoding error. See the docs of [`PointerTargetId::decode_from_rgba`] to
/// learn more.
#[derive(Copy, Clone, Debug, Fail)]
#[allow(missing_docs)]
pub enum DecodeError {
WrongAlpha(u32),
Overflow,
}
impl Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::WrongAlpha(alpha) => {
let err1 = "Failed to decode mouse target.";
let err2 = "The alpha channel should be either 0 or 255, got";
write!(f, "{} {} {}.", err1, err2, alpha)
}
Self::Overflow => {
write!(f, "ID overflow error, too many objects on the scene.")
}
}
}
}

View File

@ -49,7 +49,7 @@ impl MouseEvents {
default()
}
/// Connect the given [`ShapeViewEvents`] to the [`Events`] output.
/// Connect the given [`PointerTarget`] to the [`Events`] output.
pub fn add_sub_shape<T: DynamicShape>(&self, sub_shape: &ShapeView<T>) {
frp::extend! { network
self.frp.source.mouse_over <+ sub_shape.events.mouse_over;

View File

@ -1,17 +1,9 @@
/// This code is the body of the fragment shader main function of a GLSL shape.
// =================
// === Constants ===
// =================
/// The threshold used to decide whether a value should be included in the generated ID map. The
/// threshold is defined as 0.0 because it is failry common to use almost completely transparent
/// colors (like `Rgba(0.0, 0.0, 0.0, 0.000001)`) for shapes which should just catch mouse events
/// without providing any visual feedback.
const float ID_ALPHA_THRESHOLD = 0.0;
const int DISPLAY_MODE_NORMAL = 0;
const int DISPLAY_MODE_DEBUG_SDF = 1;
const int DISPLAY_MODE_DEBUG_ID = 2;
@ -33,12 +25,10 @@ float alpha = shape.color.color.raw.a;
// === Object ID Rendering ===
// ===========================
uvec3 chunks = encode(input_symbol_id,input_instance_id);
float alpha_no_aa = alpha > ID_ALPHA_THRESHOLD ? 1.0 : 0.0;
if (pointer_events_enabled) {
output_id = vec4(as_float_u8(chunks),alpha_no_aa);
output_id.rgb *= alpha_no_aa;
output_id = encode(input_global_instance_id,alpha_no_aa);
}
@ -60,7 +50,7 @@ if (input_display_mode == DISPLAY_MODE_NORMAL) {
output_color.rgb *= alpha_no_aa;
} else if (input_display_mode == DISPLAY_MODE_DEBUG_ID) {
float object_hue = float((input_instance_id * 7) % 100) / 100.0;
float object_hue = float((input_global_instance_id * 7) % 100) / 100.0;
Srgb object_color = srgb(hsv(object_hue, 1.0, 0.5));
output_color.rgb = object_color.raw.rgb;
output_color.a = alpha_no_aa;

View File

@ -186,18 +186,11 @@ float neg(float a) {
// === Encode ===
// This encoding must correspond to the decoding in the `Target` struct in
// src\rust\ensogl\src\display\scene.rs See there for more explanation.
uvec3 encode(int value1, int value2) {
uint chunk1 = (uint(value1) >> 4u) & 0x00FFu;
uint chunk2 = (uint(value1) & 0x000Fu) << 4u;
chunk2 = chunk2 + ((uint(value2) & 0x0F00u) >> 8u);
uint chunk3 = uint(value2) & 0x00FFu;
return uvec3(chunk1,chunk2,chunk3);
}
/// Enables check for ID encoding.
#define ID_ENCODING_OVERFLOW_CHECK
// Encodes a uint values so it can be stored in a u8 encoded float. Will clamp values that are
// out of range.
/// Encodes na [`uint`] values so it can be stored as a u8 encoded [`float`]. Will clamp values that
/// are out of range.
float as_float_u8(uint value) {
return clamp(float(value) / 255.0);
}
@ -205,3 +198,41 @@ float as_float_u8(uint value) {
vec3 as_float_u8(uvec3 v) {
return vec3(as_float_u8(v.x),as_float_u8(v.y),as_float_u8(v.z));
}
/// The threshold used to decide whether a value should be included in the generated ID map. The
/// threshold is defined as 0.0 because it is quite common to use almost completely transparent
/// colors (like `Rgba(0.0, 0.0, 0.0, 0.000001)`) for shapes which should just catch mouse events
/// without providing any visual feedback.
const float ID_ALPHA_THRESHOLD = 0.0;
/// The maximum ID that can be encoded. We are encoding IDs using rgb values (3 bytes).
const int MAX_ENCODE_ID = 256 * 256 * 256 - 1;
/// Converts provided [`int`] value to three [`u8`] chunks, skipping overflow bits.
uvec3 int_to_rgb_drop_overflow(int value) {
int r_mask = 0xFF;
int g_mask = 0xFF00;
int b_mask = 0xFF0000;
int r = (value & r_mask);
int g = (value & g_mask) >> 8;
int b = (value & b_mask) >> 16;
return uvec3(r,g,b);
}
/// This encoding must correspond to the decoding in the [`PointerTarget`] struct in the
/// `ensogl/core/src/display/scene/pointer_target.rs` file.
///
/// *Overflow Behavior*
/// If [`ID_ENCODING_OVERFLOW_CHECK`] is defined, the overflow will be reported to the CPU code as
/// part of the alpha channel. In case it is not defined, the overflow bits will be skipped and the
/// ID may alias with existing ones.
vec4 encode(int value, float alpha) {
uvec3 chunks = int_to_rgb_drop_overflow(value);
vec3 rgb = as_float_u8(chunks);
rgb *= alpha;
#ifdef ID_ENCODING_OVERFLOW_CHECK
bool is_overflow = value > MAX_ENCODE_ID;
alpha = is_overflow ? (ID_ENCODING_OVERFLOW_ERROR_CODE/255.0) : alpha;
#endif
return vec4(as_float_u8(chunks),alpha);
}

View File

@ -23,6 +23,10 @@ const COLOR: &str = include_str!("../glsl/color.glsl");
const DEBUG: &str = include_str!("../glsl/debug.glsl");
const SHAPE: &str = include_str!("../glsl/shape.glsl");
const FRAGMENT_RUNNER: &str = include_str!("../glsl/fragment_runner.glsl");
const ERROR_CODES: &[(&str, &str)] = &[(
"ID_ENCODING_OVERFLOW_ERROR_CODE",
include_str!("../glsl/error_codes/id_encoding_overflow.txt"),
)];
// === Definition ===
@ -71,13 +75,19 @@ lazy_static! {
static ref GLSL_PRELUDE: String = make_glsl_prelude();
}
fn make_error_codes() -> String {
let codes = ERROR_CODES.iter();
codes.map(|(name, code)| format!("const float {} = {}.0;", name, code.trim())).join("\n")
}
fn make_glsl_prelude() -> String {
let redirections = overload::builtin_redirections();
let math = overload::allow_overloading(MATH);
let color = overload::allow_overloading(COLOR);
let debug = overload::allow_overloading(DEBUG);
let shape = overload::allow_overloading(SHAPE);
let err_codes = make_error_codes();
let defs_header = header("SDF Primitives");
let sdf_defs = overload::allow_overloading(&primitive::all_shapes_glsl_definitions());
[redirections, math, color, debug, shape, defs_header, sdf_defs].join("\n\n")
[redirections, err_codes, math, color, debug, shape, defs_header, sdf_defs].join("\n\n")
}

View File

@ -7,12 +7,12 @@ use crate::system::gpu::types::*;
use crate::display;
use crate::display::scene::Scene;
use crate::display::shape::primitive::shader;
use crate::display::symbol;
use crate::display::symbol::geometry::compound::sprite;
use crate::display::symbol::geometry::Sprite;
use crate::display::symbol::geometry::SpriteSystem;
use crate::display::symbol::material;
use crate::display::symbol::material::Material;
use crate::system::gpu::data::attribute;
use crate::system::gpu::data::buffer::item::Storable;
use super::def;
@ -166,7 +166,7 @@ pub trait DynShapeSystemInstance: ShapeSystemInstance {
/// The dynamic shape type of this shape system definition.
type DynamicShape: DynamicShape<System = Self>;
/// New shape instantiation. Used to bind a shape to a specific scene implementation.
fn instantiate(&self, shape: &Self::DynamicShape) -> attribute::InstanceIndex;
fn instantiate(&self, shape: &Self::DynamicShape) -> symbol::GlobalInstanceId;
}
/// Abstraction for every entity which is associated with a shape system (user generated one). For
@ -191,7 +191,7 @@ pub trait Shape: display::Object + CloneRef + Debug + Sized {
/// Accessor for the underlying sprite.
fn sprite(&self) -> &Sprite;
/// Check if given mouse-event-target means this shape.
fn is_this_target(&self, target: display::scene::PointerTarget) -> bool {
fn is_this_target(&self, target: display::scene::PointerTargetId) -> bool {
self.sprite().is_this_target(target)
}
}
@ -217,7 +217,7 @@ pub trait DynamicShape: display::Object + CloneRef + Debug + Sized {
/// The "canvas" size of the shape. It defines the bounding-box for the shape drawing area.
fn size(&self) -> &DynamicParam<sprite::Size>;
/// Check if given pointer-event-target means this object.
fn is_this_target(&self, target: display::scene::PointerTarget) -> bool {
fn is_this_target(&self, target: display::scene::PointerTargetId) -> bool {
self.sprites().into_iter().any(|sprite| sprite.is_this_target(target))
}
}
@ -372,6 +372,7 @@ macro_rules! _define_shape_system {
mod shape_system_definition {
use super::*;
use $crate::display;
use $crate::display::symbol;
use $crate::display::symbol::geometry::compound::sprite;
use $crate::display::symbol::geometry::Sprite;
use $crate::system::gpu;
@ -575,14 +576,14 @@ macro_rules! _define_shape_system {
impl display::shape::DynShapeSystemInstance for ShapeSystem {
type DynamicShape = DynamicShape;
fn instantiate(&self, dyn_shape:&Self::DynamicShape)
-> gpu::data::attribute::InstanceIndex {
fn instantiate(&self, dyn_shape:&Self::DynamicShape) -> symbol::GlobalInstanceId {
let sprite = self.shape_system.new_instance();
let id = sprite.instance_id;
$(let $gpu_param = self.$gpu_param.at(id);)*
let instance_id = sprite.instance_id;
let global_id = sprite.global_instance_id;
$(let $gpu_param = self.$gpu_param.at(instance_id);)*
let shape = Shape {sprite, $($gpu_param),*};
dyn_shape.add_instance(shape);
id
global_id
}
}

View File

@ -16,6 +16,7 @@ use crate::system::gpu::data::uniform::AnyTextureUniform;
use crate::system::gpu::data::uniform::AnyUniform;
use enso_shapely::newtype_prim;
use enso_shapely::shared2;
use shader::Shader;
use wasm_bindgen::JsValue;
use web_sys::WebGlProgram;
@ -229,6 +230,47 @@ impl Drop for SymbolStatsData {
// ================================
// === GlobalInstanceIdProvider ===
// ================================
newtype_prim! {
/// Global [`Symbol`] instance id. Allows encoding symbol IDs in a texture and then decode on
/// mouse interaction.
///
/// Please see the [`fragment_runner.glsl`] file to see the encoding implementation and learn
/// more about the possible overflow behavior.
GlobalInstanceId(u32);
}
shared2! { GlobalInstanceIdProvider
/// [`GlobalInstanceId`] provider.
#[derive(Debug,Default)]
pub struct GlobalInstanceIdProviderData {
next: GlobalInstanceId,
free: Vec<GlobalInstanceId>,
}
impl {
/// Get a new [`GlobalInstanceId`] either by reusing previously disposed one or reserving a
/// new one.
pub fn reserve(&mut self) -> GlobalInstanceId {
self.free.pop().unwrap_or_else(|| {
let out = self.next;
self.next = GlobalInstanceId::new((*out) + 1);
out
})
}
/// Dispose previously used [`GlobalInstanceId`]. It will be reused for new instances.
pub fn dispose(&mut self, id: GlobalInstanceId) {
self.free.push(id);
}
}
}
// ==============
// === Symbol ===
// ==============
@ -260,7 +302,7 @@ pub struct Bindings {
newtype_prim! {
/// The ID of a [`Symbol`] instance. The ID is also the index of the symbol inside of symbol
/// registry. In case the symbol was not yet registered, the ID will be `0`.
/// registry.
SymbolId(u32);
}
@ -268,6 +310,7 @@ newtype_prim! {
#[derive(Debug, Clone, CloneRef)]
pub struct Symbol {
pub id: SymbolId,
global_id_provider: GlobalInstanceIdProvider,
display_object: display::object::Instance,
surface: Mesh,
shader: Shader,
@ -282,14 +325,21 @@ pub struct Symbol {
bindings: Rc<RefCell<Bindings>>,
stats: SymbolStats,
is_hidden: Rc<Cell<bool>>,
global_instance_id: Buffer<i32>,
}
impl Symbol {
/// Create new instance with the provided on-dirty callback.
pub fn new<OnMut: Fn() + Clone + 'static>(stats: &Stats, id: SymbolId, on_mut: OnMut) -> Self {
pub fn new<OnMut: Fn() + Clone + 'static>(
stats: &Stats,
id: SymbolId,
global_id_provider: &GlobalInstanceIdProvider,
on_mut: OnMut,
) -> Self {
let logger = Logger::new(format!("symbol_{}", id));
let init_logger = logger.clone();
debug!(init_logger, "Initializing.", || {
let global_id_provider = global_id_provider.clone_ref();
let on_mut2 = on_mut.clone();
let surface_logger = Logger::new_sub(&logger, "surface");
let shader_logger = Logger::new_sub(&logger, "shader");
@ -308,8 +358,13 @@ impl Symbol {
let symbol_id_uniform = variables.add_or_panic("symbol_id", (*id) as i32);
let display_object = display::object::Instance::new(logger.clone());
let is_hidden = Rc::new(Cell::new(false));
let instance_scope = surface.instance_scope();
let global_instance_id = instance_scope.add_buffer("global_instance_id");
Self {
id,
global_id_provider,
display_object,
surface,
shader,
@ -322,6 +377,7 @@ impl Symbol {
bindings,
stats,
is_hidden,
global_instance_id,
}
.init()
})
@ -343,6 +399,10 @@ impl Symbol {
self
}
pub fn new_instance(&self) -> SymbolInstance {
SymbolInstance::new(self)
}
pub(crate) fn set_context(&self, context: Option<&Context>) {
*self.context.borrow_mut() = context.cloned();
self.surface.set_context(context);
@ -657,3 +717,45 @@ impl display::Object for Symbol {
&self.display_object
}
}
// ======================
// === SymbolInstance ===
// ======================
/// Instance of a [`Symbol`]. It does not define any custom parameters, however, it manages the
/// [`InstanceIndex`] and [`GlobalInstanceId`] ones.
#[derive(Debug, Clone, CloneRef, Deref)]
pub struct SymbolInstance {
rc: Rc<SymbolInstanceData>,
}
#[derive(Debug, NoCloneBecauseOfCustomDrop)]
pub struct SymbolInstanceData {
pub symbol: Symbol,
pub instance_id: attribute::InstanceIndex,
pub global_instance_id: GlobalInstanceId,
}
impl SymbolInstance {
fn new(symbol: &Symbol) -> Self {
let symbol = symbol.clone_ref();
let global_instance_id = symbol.global_id_provider.reserve();
let instance_id = symbol.surface().instance_scope().add_instance();
let global_instance_id_attr = symbol.global_instance_id.at(instance_id);
global_instance_id_attr.set(*global_instance_id as i32);
let data = SymbolInstanceData { symbol, instance_id, global_instance_id };
let rc = Rc::new(data);
Self { rc }
}
}
impl Drop for SymbolInstanceData {
fn drop(&mut self) {
self.symbol.surface().instance_scope().dispose(self.instance_id);
self.symbol.global_id_provider.dispose(self.global_instance_id);
}
}

View File

@ -8,12 +8,14 @@ use crate::system::gpu::types::*;
use crate::debug::Stats;
use crate::display;
use crate::display::attribute::EraseOnDrop;
use crate::display::layout::alignment;
use crate::display::layout::Alignment;
use crate::display::scene::Scene;
use crate::display::symbol::material::Material;
use crate::display::symbol::Symbol;
use crate::display::symbol::SymbolId;
use crate::display::symbol::SymbolInstance;
@ -52,45 +54,6 @@ impl Drop for SpriteStats {
// ===================
// === SpriteGuard ===
// ===================
/// Lifetime guard for `Sprite`. After sprite is dropped, it is removed from the sprite system.
/// Note that the removal does not involve many changes to buffers. What really happens is setting
/// the sprite dimensions to zero and marking it index as a free for future reuse.
#[derive(Debug)]
pub struct SpriteGuard {
instance_id: attribute::InstanceIndex,
symbol: Symbol,
size: Attribute<Vector2<f32>>,
display_object: display::object::Instance,
}
impl SpriteGuard {
fn new(
instance_id: attribute::InstanceIndex,
symbol: &Symbol,
size: &Attribute<Vector2<f32>>,
display_object: &display::object::Instance,
) -> Self {
let symbol = symbol.clone_ref();
let size = size.clone_ref();
let display_object = display_object.clone_ref();
Self { instance_id, symbol, size, display_object }
}
}
impl Drop for SpriteGuard {
fn drop(&mut self) {
self.size.set(zero());
self.symbol.surface().instance_scope().dispose(self.instance_id);
self.display_object.unset_parent();
}
}
// ============
// === Size ===
// ============
@ -159,32 +122,45 @@ impl Size {
#[allow(missing_docs)]
pub struct Sprite {
pub symbol: Symbol,
pub instance_id: attribute::InstanceIndex,
pub instance: SymbolInstance,
pub size: Size,
display_object: display::object::Instance,
transform: Attribute<Matrix4<f32>>,
stats: Rc<SpriteStats>,
guard: Rc<SpriteGuard>,
erase_on_drop: Rc<EraseOnDrop<Attribute<Vector2<f32>>>>,
unset_parent_on_drop: Rc<display::object::UnsetParentOnDrop>,
}
impl Sprite {
/// Constructor.
pub fn new(
symbol: &Symbol,
instance_id: attribute::InstanceIndex,
instance: SymbolInstance,
transform: Attribute<Matrix4<f32>>,
size: Attribute<Vector2<f32>>,
stats: &Stats,
) -> Self {
let symbol = symbol.clone_ref();
let logger = Logger::new(iformat!("Sprite{instance_id}"));
let logger = Logger::new(iformat!("Sprite{instance.instance_id}"));
let display_object = display::object::Instance::new(logger);
let stats = Rc::new(SpriteStats::new(stats));
let guard = Rc::new(SpriteGuard::new(instance_id, &symbol, &size, &display_object));
let erase_on_drop = Rc::new(EraseOnDrop::new(size.clone_ref()));
let size = Size::new(size);
let unset_parent_on_drop =
Rc::new(display::object::UnsetParentOnDrop::new(&display_object));
let default_size = Vector2(DEFAULT_SPRITE_SIZE.0, DEFAULT_SPRITE_SIZE.1);
size.set(default_size);
Self { symbol, instance_id, size, display_object, transform, stats, guard }.init()
Self {
symbol,
instance,
size,
display_object,
transform,
stats,
erase_on_drop,
unset_parent_on_drop,
}
.init()
}
/// Init display object bindings. In particular defines the behavior of the show and hide
@ -204,11 +180,10 @@ impl Sprite {
}
/// Check if given pointer-event-target means this object.
pub fn is_this_target(&self, target: display::scene::PointerTarget) -> bool {
pub fn is_this_target(&self, target: display::scene::PointerTargetId) -> bool {
match target {
display::scene::PointerTarget::Background => false,
display::scene::PointerTarget::Symbol { symbol_id, instance_id } =>
self.symbol_id() == symbol_id && self.instance_id == instance_id,
display::scene::PointerTargetId::Background => false,
display::scene::PointerTargetId::Symbol { id } => self.global_instance_id == id,
}
}
}
@ -219,6 +194,13 @@ impl display::Object for Sprite {
}
}
impl Deref for Sprite {
type Target = SymbolInstance;
fn deref(&self) -> &Self::Target {
&self.instance
}
}
// ====================
@ -265,10 +247,10 @@ impl SpriteSystem {
/// Creates a new sprite instance.
pub fn new_instance(&self) -> Sprite {
let instance_id = self.symbol.surface().instance_scope().add_instance();
let transform = self.transform.at(instance_id);
let size = self.size.at(instance_id);
let sprite = Sprite::new(&self.symbol, instance_id, transform, size, &self.stats);
let instance = self.symbol.new_instance();
let transform = self.transform.at(instance.instance_id);
let size = self.size.at(instance.instance_id);
let sprite = Sprite::new(&self.symbol, instance, transform, size, &self.stats);
self.add_child(&sprite);
sprite
}
@ -341,16 +323,18 @@ impl SpriteSystem {
material.add_input_def::<Matrix4<f32>>("transform");
material.add_input_def::<Matrix4<f32>>("view_projection");
material.add_input_def::<Vector2<f32>>("alignment");
material.add_input_def::<i32>("global_instance_id");
material.add_output_def::<Vector3<f32>>("local");
material.add_output_def::<i32>("instance_id");
material.set_main(
"
mat4 model_view_projection = input_view_projection * input_transform;
input_local = vec3((input_uv - input_alignment) * input_size, 0.0);
gl_Position = model_view_projection * vec4(input_local,1.0);
input_local.z = gl_Position.z;
input_instance_id = gl_InstanceID;
",
// This is left here in case it will be needed. The `instance_id` is the same as the
// built-in `gl_InstanceID` and can be implemented very efficiently:
// input_instance_id = gl_InstanceID;
);
material
}

View File

@ -8,7 +8,7 @@ use crate::data::dirty;
use crate::debug::stats::Stats;
use crate::system::Context;
use enso_shapely::shared;
use enso_shapely::shared2;
use num_enum::IntoPrimitive;
@ -90,9 +90,9 @@ macro_rules! update_scopes {
// === Definition ===
shared! { Mesh
shared2! { Mesh
/// A polygon mesh is a collection of vertices, edges and faces that defines the shape of a
/// polyhedral object. Mesh describes the shape of the display element. It consist of several
/// polyhedral object. Mesh describes the shape of the display element. It consists of several
/// scopes containing sets of variables. See the documentation of `Scopes` to learn more.
///
/// Please note, that there are other, higher-level scopes defined by other structures, including:
@ -101,7 +101,7 @@ shared! { Mesh
/// Object refers to the whole geometry with all of its instances.
///
/// - Global Scope
/// Global scope is shared by all objects and it contains some universal global variables, like
/// Global scope is shared by all objects, and it contains some universal global variables, like
/// the current 'time' counter.
///
/// Each scope can contain named attributes which can be accessed from within materials. If the same

View File

@ -7,6 +7,7 @@ use crate::prelude::*;
use crate::data::dirty;
use crate::debug::stats::Stats;
use crate::display::camera::Camera2d;
use crate::display::symbol;
use crate::display::symbol::Symbol;
use crate::display::symbol::SymbolId;
use crate::system::gpu::data::uniform::Uniform;
@ -36,6 +37,7 @@ pub type SymbolDirty = dirty::SharedSet<SymbolId, Box<dyn Fn()>>;
#[derive(Clone, CloneRef, Debug)]
pub struct SymbolRegistry {
symbols: Rc<RefCell<OptVec<Symbol>>>,
global_id_provider: symbol::GlobalInstanceIdProvider,
symbol_dirty: SymbolDirty,
logger: Logger,
view_projection: Uniform<Matrix4<f32>>,
@ -63,7 +65,18 @@ impl SymbolRegistry {
let z_zoom_1 = variables.add_or_panic("z_zoom_1", 1.0);
let context = default();
let stats = stats.clone_ref();
Self { symbols, symbol_dirty, logger, view_projection, z_zoom_1, variables, context, stats }
let global_id_provider = default();
Self {
symbols,
global_id_provider,
symbol_dirty,
logger,
view_projection,
z_zoom_1,
variables,
context,
stats,
}
}
/// Creates a new `Symbol` instance and returns its id.
@ -73,7 +86,7 @@ impl SymbolRegistry {
let index = self.symbols.borrow_mut().insert_with_ix_(|ix| {
let id = SymbolId::new(ix as u32);
let on_mut = move || symbol_dirty.set(id);
let symbol = Symbol::new(stats, id, on_mut);
let symbol = Symbol::new(stats, id, &self.global_id_provider, on_mut);
symbol.set_context(self.context.borrow().as_ref());
symbol
});

View File

@ -62,6 +62,7 @@ pub struct ShaderData {
geometry_material : Material,
surface_material : Material,
program : Option<WebGlProgram>,
shader : Option<builder::Shader>,
dirty : Dirty,
logger : Logger,
stats : Stats,
@ -97,10 +98,11 @@ impl {
let geometry_material = default();
let surface_material = default();
let program = default();
let shader = default();
let dirty_logger = Logger::new_sub(&logger,"dirty");
let dirty = Dirty::new(dirty_logger,Box::new(on_mut));
let stats = stats.clone_ref();
Self {context,geometry_material,surface_material,program,dirty,logger,stats}
Self {context,geometry_material,surface_material,program,shader,dirty,logger,stats}
}
/// Check dirty flags and update the state accordingly.
@ -146,7 +148,10 @@ impl {
let shader = shader_builder.build();
let program = compile_program(context,&shader.vertex,&shader.fragment);
match program {
Ok(program) => { self.program = Some(program);}
Ok(program) => {
self.program = Some(program);
self.shader = Some(shader);
}
Err(ContextLossOrError::Error(err)) => error!(self.logger, "{err}"),
Err(ContextLossOrError::ContextLoss) => {}
}
@ -163,6 +168,11 @@ impl {
let surface_material_inputs = self.surface_material.inputs().clone();
geometry_material_inputs.into_iter().chain(surface_material_inputs).collect()
}
/// Get the generated shader, if it was already generated.
pub fn shader(&self) -> Option<builder::Shader> {
self.shader.clone()
}
}}
impl Drop for ShaderData {

View File

@ -108,8 +108,19 @@ pub struct AttributeQualifier {
}
impl AttributeQualifier {
pub fn to_input_var<Name: Into<glsl::Identifier>>(&self, name: Name) -> glsl::GlobalVar {
let storage = self.storage;
/// Convert the qualifier to input variable definition. The [`use_qual`] parameter defines if
/// the qualified storage interpolator (e.g. `flat`) should be used. Interpolation modifiers are
/// illegal on vertex shader input attributes, however, they are required on fragment shader
/// ones.
pub fn to_input_var<Name: Into<glsl::Identifier>>(
&self,
name: Name,
use_qual: bool,
) -> glsl::GlobalVar {
let mut storage = self.storage;
if !use_qual {
storage.interpolation = None;
}
glsl::GlobalVar {
layout: None,
storage: Some(glsl::GlobalVarStorage::InStorage(storage)),
@ -320,9 +331,9 @@ impl ShaderBuilder {
let vert_name = mk_vertex_name(name);
let frag_name = mk_fragment_name(name);
let sharing = glsl::Assignment::new(&frag_name, &vert_name);
self.vertex.add(qual.to_input_var(&vert_name));
self.vertex.add(qual.to_input_var(&vert_name, false));
self.vertex.add(qual.to_output_var(&frag_name));
self.fragment.add(qual.to_input_var(&frag_name));
self.fragment.add(qual.to_input_var(&frag_name, true));
self.vertex.main.add(sharing);
}
}
@ -333,7 +344,7 @@ impl ShaderBuilder {
for (name, qual) in &cfg.shared {
let frag_name = mk_fragment_name(name);
self.vertex.add(qual.to_output_var(&frag_name));
self.fragment.add(qual.to_input_var(&frag_name));
self.fragment.add(qual.to_input_var(&frag_name, true));
}
}
}

View File

@ -258,10 +258,10 @@ impl WorldData {
}
fn init_composer(&self) {
let mouse_hover_ids = self.default_scene.mouse.hover_ids.clone_ref();
let mouse_hover_rgba = self.default_scene.mouse.hover_rgba.clone_ref();
let mut pixel_read_pass = PixelReadPass::<u8>::new(&self.default_scene.mouse.position);
pixel_read_pass.set_callback(move |v| {
mouse_hover_ids.set(Vector4::from_iterator(v.iter().map(|value| *value as u32)))
mouse_hover_rgba.set(Vector4::from_iterator(v.iter().map(|value| *value as u32)))
});
// TODO: We may want to enable it on weak hardware.
// pixel_read_pass.set_threshold(1);

View File

@ -1,7 +1,4 @@
//! Root module for GUI related components.
//! NOTE
//! This file is under a heavy development. It contains commented lines of code and some code may
//! be of poor quality. Expect drastic changes.
use crate::display::object::traits::*;
use crate::prelude::*;
@ -9,72 +6,18 @@ use crate::prelude::*;
use crate::display;
use crate::display::scene;
use crate::display::scene::layer::WeakLayer;
use crate::display::scene::MouseTarget;
use crate::display::scene::Scene;
use crate::display::scene::ShapeRegistry;
use crate::display::shape::primitive::system::DynamicShape;
use crate::display::shape::primitive::system::DynamicShapeInternals;
use crate::display::symbol::SymbolId;
use crate::system::gpu::data::attribute;
use enso_frp as frp;
use crate::display::symbol;
// ==============
// === Export ===
// ==============
// =======================
// === ShapeViewEvents ===
// =======================
/// FRP event endpoints exposed by each shape view. In particular these are all mouse events
/// which are triggered by mouse interactions after the shape view is placed on the scene.
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct ShapeViewEvents {
pub network: frp::Network,
pub mouse_up: frp::Source,
pub mouse_down: frp::Source,
pub mouse_over: frp::Source,
pub mouse_out: frp::Source,
pub on_drop: frp::Source,
}
impl ShapeViewEvents {
fn new() -> Self {
frp::new_network! { network
on_drop <- source_();
mouse_down <- source_();
mouse_up <- source_();
mouse_over <- source_();
mouse_out <- source_();
is_mouse_over <- bool(&mouse_out,&mouse_over);
out_on_drop <- on_drop.gate(&is_mouse_over);
eval_ out_on_drop (mouse_out.emit(()));
}
Self { network, mouse_up, mouse_down, mouse_over, mouse_out, on_drop }
}
}
impl MouseTarget for ShapeViewEvents {
fn mouse_down(&self) -> &frp::Source {
&self.mouse_down
}
fn mouse_up(&self) -> &frp::Source {
&self.mouse_up
}
fn mouse_over(&self) -> &frp::Source {
&self.mouse_over
}
fn mouse_out(&self) -> &frp::Source {
&self.mouse_out
}
}
impl Default for ShapeViewEvents {
fn default() -> Self {
Self::new()
}
}
pub use crate::display::scene::PointerTarget;
@ -136,9 +79,9 @@ impl<S> HasContent for ShapeView<S> {
#[allow(missing_docs)]
pub struct ShapeViewModel<S> {
shape: S,
pub events: ShapeViewEvents,
pub events: PointerTarget,
pub registry: RefCell<Option<ShapeRegistry>>,
pub pointer_targets: RefCell<Vec<(SymbolId, attribute::InstanceIndex)>>,
pub pointer_targets: RefCell<Vec<symbol::GlobalInstanceId>>,
}
impl<S> Deref for ShapeViewModel<S> {
@ -189,7 +132,7 @@ impl<S: DynamicShape> ShapeViewModel<S> {
/// Constructor.
pub fn new(logger: impl AnyLogger) -> Self {
let shape = S::new(logger);
let events = ShapeViewEvents::new();
let events = PointerTarget::new();
let registry = default();
let pointer_targets = default();
ShapeViewModel { shape, events, registry, pointer_targets }
@ -197,12 +140,8 @@ impl<S: DynamicShape> ShapeViewModel<S> {
fn add_to_scene_layer(&self, scene: &Scene, layer: &scene::Layer) {
let instance = layer.instantiate(scene, &self.shape);
scene.shapes.insert_mouse_target(
instance.symbol_id,
instance.instance_id,
self.events.clone_ref(),
);
self.pointer_targets.borrow_mut().push((instance.symbol_id, instance.instance_id));
scene.shapes.insert_mouse_target(instance.global_instance_id, self.events.clone_ref());
self.pointer_targets.borrow_mut().push(instance.global_instance_id);
*self.registry.borrow_mut() = Some(scene.shapes.clone_ref());
}
}
@ -210,8 +149,8 @@ impl<S: DynamicShape> ShapeViewModel<S> {
impl<S> ShapeViewModel<S> {
fn unregister_existing_mouse_targets(&self) {
if let Some(registry) = &*self.registry.borrow() {
for (symbol_id, instance_id) in mem::take(&mut *self.pointer_targets.borrow_mut()) {
registry.remove_mouse_target(symbol_id, instance_id);
for global_instance_id in mem::take(&mut *self.pointer_targets.borrow_mut()) {
registry.remove_mouse_target(global_instance_id);
}
}
}

View File

@ -5,6 +5,7 @@
#![recursion_limit = "512"]
// === Features ===
#![allow(incomplete_features)]
#![feature(negative_impls)]
#![feature(associated_type_defaults)]
#![feature(bool_to_option)]
#![feature(cell_update)]

View File

@ -252,3 +252,45 @@ impl<T: Storable> CellSetter for Attribute<T> {
self.buffer.set(self.index.into(), value);
}
}
impl<T: Storable + Default> Erase for Attribute<T> {
fn erase(&self) {
self.set(default())
}
}
// =============
// === Erase ===
// =============
/// Generalization for internally mutable structures which can be erased.
///
/// For now, it is placed here, as only [`Attribute`] uses it, but it might be refactored in the
/// future if it will be usable in more places.
#[allow(missing_docs)]
pub trait Erase {
fn erase(&self);
}
/// The provided element will be erased whenever this structure is dropped. Please note that the
///provided element implements [`CloneRef`] it can still be referenced after this struct is
/// dropped.
#[derive(Debug, NoCloneBecauseOfCustomDrop)]
pub struct EraseOnDrop<T: Erase> {
elem: T,
}
impl<T: Erase> EraseOnDrop<T> {
/// Constructor.
pub fn new(elem: T) -> Self {
Self { elem }
}
}
impl<T: Erase> Drop for EraseOnDrop<T> {
fn drop(&mut self) {
self.elem.erase()
}
}

View File

@ -217,18 +217,24 @@ impl<T:Storable> {
/// https://stackoverflow.com/questions/38853096/webgl-how-to-bind-values-to-a-mat4-attribute
pub fn vertex_attrib_pointer(&self, loc:u32, instanced:bool) {
if let Some(gl) = &self.gl {
let is_integer = T::is_integer();
let item_byte_size = T::item_gpu_byte_size() as i32;
let item_type = T::item_gl_enum().into();
let rows = T::rows() as i32;
let cols = T::cols() as i32;
let col_byte_size = item_byte_size * rows;
let stride = col_byte_size * cols;
let normalize = false;
for col in 0..cols {
let lloc = loc + col as u32;
let off = col * col_byte_size;
gl.context.enable_vertex_attrib_array(lloc);
gl.context.vertex_attrib_pointer_with_i32(lloc,rows,item_type,normalize,stride,off);
if is_integer {
gl.context.vertex_attrib_i_pointer_with_i32(lloc,rows,item_type,stride,off);
} else {
let normalize = false;
gl.context.vertex_attrib_pointer_with_i32
(lloc,rows,item_type,normalize,stride,off);
}
if instanced {
let instance_count = 1;
gl.context.vertex_attrib_divisor(lloc,instance_count);

View File

@ -71,6 +71,11 @@ pub trait Storable: BufferItemBounds {
/// The number of columns of the type encoded as 2d matrix.
type Cols: DimName;
/// Checks if the type should be interpreted as integer. This is important when binding the
/// buffer to the shader. See WebGL's `vertexAttribPointer` vs `vertexAttribIPointer` to learn
/// more.
fn is_integer() -> bool;
// === Size ===
@ -142,6 +147,10 @@ impl Storable for bool {
type Rows = U1;
type Cols = U1;
fn is_integer() -> bool {
false
}
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
buffer
}
@ -161,6 +170,10 @@ impl Storable for i32 {
type Rows = U1;
type Cols = U1;
fn is_integer() -> bool {
true
}
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
buffer
}
@ -180,6 +193,10 @@ impl Storable for u32 {
type Rows = U1;
type Cols = U1;
fn is_integer() -> bool {
true
}
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
buffer
}
@ -199,6 +216,10 @@ impl Storable for f32 {
type Rows = U1;
type Cols = U1;
fn is_integer() -> bool {
false
}
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
buffer
}
@ -223,6 +244,10 @@ where
type Rows = R;
type Cols = C;
fn is_integer() -> bool {
T::is_integer()
}
#[allow(unsafe_code)]
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
// This code casts slice to matrix. This is safe because `MatrixMN`

View File

@ -2,9 +2,11 @@
#![allow(missing_docs)]
use enso_prelude::*;
use enso_web::traits::*;
use crate::system::Context;
use enso_web as web;
use js_sys::Float32Array;
use web_sys::WebGlBuffer;
use web_sys::WebGlProgram;
@ -47,12 +49,44 @@ pub type Program = WebGlProgram;
// === Error ===
// =============
#[derive(Debug, Fail, From)]
pub enum Error {
#[derive(Debug, Fail)]
pub enum SingleTargetError {
#[fail(display = "Unable to create {}.", target)]
Create { target: ErrorTarget },
#[fail(display = "Unable to compile {}.\n{}\n\n{}", target, message, code)]
Compile { target: ErrorTarget, message: String, code: String },
#[fail(display = "Unable to compile {}.\n{}\n\n{}", target, message, preview_code)]
Compile { target: ErrorTarget, message: String, preview_code: String },
}
#[derive(Debug, Fail, From)]
pub enum Error {
Create {
target: ErrorTarget,
},
Compile {
js_path: String,
vertex: Option<SingleTargetError>,
fragment: Option<SingleTargetError>,
},
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Create { target } => write!(f, "Unable to create {}", target),
Self::Compile { js_path, vertex, fragment } => {
let vtx_msg = vertex.as_ref().map(|t| format!("\n\n{}", t)).unwrap_or_default();
let frag_msg = fragment.as_ref().map(|t| format!("\n\n{}", t)).unwrap_or_default();
let run_msg = |n| {
format!("Run `console.log({}.{})` to inspect the {} shader.", js_path, n, n)
};
let vtx_run_msg = run_msg("vertex");
let frag_run_msg = run_msg("fragment");
let err_msg = "Unable to create shader.";
write!(f, "{}\n{}\n{}{}{}", err_msg, vtx_run_msg, frag_run_msg, vtx_msg, frag_msg)
}
}
}
}
#[derive(Copy, Clone, Debug, Fail)]
@ -120,17 +154,17 @@ fn unwrap_error(opt_err: Option<String>) -> String {
// === Compile / Link ===
// ======================
pub fn compile_vertex_shader(ctx: &Context, src: &str) -> Result<Shader, Error> {
pub fn compile_vertex_shader(ctx: &Context, src: &str) -> Result<Shader, SingleTargetError> {
compile_shader(ctx, Context::VERTEX_SHADER, src)
}
pub fn compile_fragment_shader(ctx: &Context, src: &str) -> Result<Shader, Error> {
pub fn compile_fragment_shader(ctx: &Context, src: &str) -> Result<Shader, SingleTargetError> {
compile_shader(ctx, Context::FRAGMENT_SHADER, src)
}
pub fn compile_shader(ctx: &Context, tp: u32, src: &str) -> Result<Shader, Error> {
pub fn compile_shader(ctx: &Context, tp: u32, src: &str) -> Result<Shader, SingleTargetError> {
let target = ErrorTarget::Shader;
let shader = ctx.create_shader(tp).ok_or(Error::Create { target })?;
let shader = ctx.create_shader(tp).ok_or(SingleTargetError::Create { target })?;
ctx.shader_source(&shader, src);
ctx.compile_shader(&shader);
match shader.check(ctx) {
@ -146,7 +180,7 @@ pub fn compile_shader(ctx: &Context, tp: u32, src: &str) -> Result<Shader, Error
let lines_with_num = lines_with_num.collect::<Vec<String>>();
let code_with_num = lines_with_num.join("\n");
let error_loc_pfx = "ERROR: 0:";
let out = if let Some(msg) = message.strip_prefix(error_loc_pfx) {
let preview_code = if let Some(msg) = message.strip_prefix(error_loc_pfx) {
let line_num: String = msg.chars().take_while(|c| c.is_digit(10)).collect();
let line_num = line_num.parse::<usize>().unwrap() - 1;
let preview_radius = 5;
@ -156,7 +190,7 @@ pub fn compile_shader(ctx: &Context, tp: u32, src: &str) -> Result<Shader, Error
} else {
code_with_num
};
Err(Error::Compile { target, message, code: out })
Err(SingleTargetError::Compile { target, message, preview_code })
}
}
}
@ -197,9 +231,29 @@ pub fn compile_program(
vert_src: &str,
frag_src: &str,
) -> Result<Program, ContextLossOrError> {
let vert_shader = compile_vertex_shader(ctx, vert_src).map_err(ContextLossOrError::Error)?;
let frag_shader = compile_fragment_shader(ctx, frag_src).map_err(ContextLossOrError::Error)?;
link_program(ctx, &vert_shader, &frag_shader)
let vert_shader = compile_vertex_shader(ctx, vert_src);
let frag_shader = compile_fragment_shader(ctx, frag_src);
match (vert_shader, frag_shader) {
(Ok(vert_shader), Ok(frag_shader)) => link_program(ctx, &vert_shader, &frag_shader),
(vert_shader, frag_shader) => {
let vertex = vert_shader.err();
let fragment = frag_shader.err();
// FIXME: this should be taken from config and refactored to a function
let path = &["enso", "debug", "shader"];
let shader_dbg = web::Reflect::get_nested_object_or_create(&web::window, path);
let shader_dbg = shader_dbg.unwrap();
let len = web::Object::keys(&shader_dbg).length();
let debug_var_name = format!("shader_{}", len);
let shader_tgt_dbg = web::Object::new();
web::Reflect::set(&shader_dbg, &(&debug_var_name).into(), &shader_tgt_dbg).ok();
web::Reflect::set(&shader_tgt_dbg, &"vertex".into(), &vert_src.into()).ok();
web::Reflect::set(&shader_tgt_dbg, &"fragment".into(), &frag_src.into()).ok();
let js_path = format!("window.{}.{}", path.join("."), debug_var_name);
Err(ContextLossOrError::Error(Error::Compile { js_path, vertex, fragment }))
}
}
}

View File

@ -10,6 +10,7 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
ensogl-example-animation = { path = "animation" }
ensogl-example-complex-shape-system = { path = "complex-shape-system" }
ensogl-example-custom-shape-system = { path = "custom-shape-system" }
ensogl-example-dom-symbols = { path = "dom-symbols" }
ensogl-example-drop-manager = { path = "drop-manager" }
ensogl-example-easing-animator = { path = "easing-animator" }

View File

@ -35,9 +35,9 @@ use logger::TraceLogger as Logger;
// ===================
/// An entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_animation() {
pub fn main() {
run_once_initialized(|| {
let app = Application::new("root");
let logger: Logger = Logger::new("AnimationTest");

View File

@ -55,9 +55,9 @@ mod mask {
// ===================
/// The example entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_complex_shape_system() {
pub fn main() {
let world = World::new().displayed_in("root");
let scene = &world.default_scene;
let camera = scene.camera().clone_ref();

View File

@ -0,0 +1,15 @@
[package]
name = "ensogl-example-custom-shape-system"
version = "0.1.0"
authors = ["Enso Team <contact@enso.org>"]
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
ensogl-core = { path = "../../core" }
enso-frp = { path = "../../../frp" }
wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] }
enso-profiler = { path = "../../../profiler"}

View File

@ -0,0 +1,76 @@
//! Example scene showing simple usage of a shape system.
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
use ensogl_core::display::shape::*;
use ensogl_core::display::world::*;
use ensogl_core::prelude::*;
use wasm_bindgen::prelude::*;
use enso_frp as frp;
use ensogl_core::data::color;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::object::ObjectOps;
// ==============
// === Shapes ===
// ==============
mod shape {
use super::*;
ensogl_core::define_shape_system! {
(style:Style) {
let circle1 = Circle(50.px());
let circle_bg = circle1.translate_x(-(50.0.px()));
let circle_sub = circle1.translate_y(-(50.0.px()));
let rect = Rect((100.0.px(),100.0.px()));
let shape = circle_bg + rect - circle_sub;
let shape = shape.fill(color::Rgba::new(0.3, 0.3, 0.3, 1.0));
shape.into()
}
}
}
// ===================
// === Entry Point ===
// ===================
/// The example entry point.
#[entry_point]
#[allow(dead_code)]
pub fn main() {
let world = World::new().displayed_in("root");
let scene = &world.default_scene;
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let logger = Logger::new("ShapeView");
let view1 = shape::View::new(&logger);
view1.size.set(Vector2::new(300.0, 300.0));
view1.mod_position(|t| *t = Vector3::new(50.0, 50.0, 0.0));
world.add_child(&view1);
world.keep_alive_forever();
frp::new_network! { network
trace view1.events.mouse_over;
trace view1.events.mouse_out;
trace view1.events.mouse_down;
}
world
.on
.before_frame
.add(move |_time| {
let _keep_alive = &network;
let _keep_alive = &view1;
let _keep_alive = &navigator;
})
.forget();
}

View File

@ -32,10 +32,10 @@ use nalgebra::Vector3;
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
#[allow(clippy::many_single_char_names)]
pub fn entry_point_dom_symbols() {
pub fn main() {
let world = World::new().displayed_in("root");
let scene = &world.default_scene;
let camera = scene.camera();

View File

@ -52,9 +52,9 @@ fn download_file(file: ensogl_drop_manager::File) {
}
/// The example entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_drop_manager() {
pub fn main() {
let world = World::new().displayed_in("root");
let drop_manager = ensogl_drop_manager::Manager::new(world.default_scene.dom.root.as_ref());
let network = enso_frp::Network::new("Debug Scene");

View File

@ -285,10 +285,10 @@ macro_rules! examples {
)*};
}
#[wasm_bindgen]
#[allow(dead_code)]
/// Runs EasingAnimator example.
pub fn entry_point_easing_animator() {
#[entry_point]
#[allow(dead_code)]
pub fn main() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
let container = web::document.create_div_or_panic();

View File

@ -31,9 +31,9 @@ use ensogl_text_msdf_sys::run_once_initialized;
/// Main example runner.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_glyph_system() {
pub fn main() {
run_once_initialized(|| init(&World::new().displayed_in("root")));
}

View File

@ -38,9 +38,9 @@ use logger::TraceLogger as Logger;
// ===================
/// An entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_list_view() {
pub fn main() {
run_once_initialized(|| {
let app = Application::new("root");
init(&app);

View File

@ -106,12 +106,10 @@ impl View {
pub fn new(app: &Application) -> Self {
let frp = Frp::new();
let model = Model::new(app);
let events = &model.shape.events;
let network = &events.network;
let network = &frp.network;
frp::extend! { network
// FIXME [mwu] Currently only `mouse_over` and `mouse_out` events are delivered.
// See: https://github.com/enso-org/ide/issues/1477
trace model.shape.events.mouse_up;
trace model.shape.events.mouse_release;
trace model.shape.events.mouse_down;
trace model.shape.events.mouse_over;
trace model.shape.events.mouse_out;
@ -159,9 +157,9 @@ impl application::View for View {
// ===================
/// The example entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_mouse_events() {
pub fn main() {
run_once_initialized(|| {
let app = Application::new("root");
let shape: View = app.new_view();

View File

@ -34,9 +34,9 @@ use ensogl_flame_graph as flame_graph;
// ===================
/// The example entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_profiling_run_graph() {
pub fn main() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();

View File

@ -31,9 +31,9 @@ use ensogl_flame_graph as flame_graph;
// ===================
/// Render a graph of a profile file.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub async fn entry_point_render_profile() {
pub async fn main() {
use ensogl_core::display::object::ObjectOps;
let app = application::Application::new("root");
let world = &app.display;

View File

@ -43,8 +43,8 @@ use ensogl_text_msdf_sys::run_once_initialized;
// ===================
/// An entry point.
#[wasm_bindgen]
pub fn entry_point_scroll_area() {
#[entry_point]
pub fn main() {
run_once_initialized(|| {
let app = Application::new("root");
init(&app);

View File

@ -11,3 +11,4 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
ensogl-core = { path = "../../core" }
wasm-bindgen = { version = "0.2.78", features = [ "nightly" ] }
enso-profiler = { path = "../../../profiler"}

View File

@ -54,9 +54,9 @@ pub fn shape() -> AnyShape {
// ===================
/// The example entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_shape_system() {
pub fn main() {
let world = World::new().displayed_in("root");
let scene = &world.default_scene;
let camera = scene.camera().clone_ref();
@ -70,12 +70,19 @@ pub fn entry_point_shape_system() {
world.add_child(&sprite_system);
world.keep_alive_forever();
let mut i = 0;
world
.on
.before_frame
.add(move |_time| {
let _keep_alive = &sprite;
let _keep_alive = &navigator;
i += 1;
if i == 5 {
let shader = sprite.symbol.shader().shader().unwrap();
DEBUG!("\n\nVERTEX:\n{shader.vertex}");
DEBUG!("\n\nFRAGMENT:\n{shader.fragment}");
}
})
.forget();
}

View File

@ -38,9 +38,9 @@ use ensogl_text_msdf_sys::run_once_initialized;
// ===================
/// An entry point.
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_slider() {
pub fn main() {
run_once_initialized(|| {
let app = Application::new("root");
init(&app);

View File

@ -31,9 +31,9 @@ use nalgebra::Vector3;
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_sprite_system_benchmark() {
pub fn main() {
let world = World::new().displayed_in("root");
let scene = &world.default_scene;
let camera = scene.camera().clone_ref();

View File

@ -26,9 +26,9 @@ use ensogl_core::display::symbol::geometry::SpriteSystem;
#[wasm_bindgen]
#[entry_point]
#[allow(dead_code)]
pub fn entry_point_sprite_system() {
pub fn main() {
let world = World::new().displayed_in("root");
let navigator = Navigator::new(&world.default_scene, &world.default_scene.camera());
let sprite_system = SpriteSystem::new(&world);

View File

@ -20,7 +20,7 @@ use nalgebra::Vector2;
/// JS supports up to 5 mouse buttons currently:
/// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
/// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum Button {
Button0,
@ -57,7 +57,12 @@ impl Button {
/// Construct a button from the provided code point. In case the code is unrecognized, the
/// default button will be returned.
pub fn from_code(code: i32) -> Self {
Self::try_from_code(code).unwrap_or_default()
Self::try_from_code(code).unwrap_or_else(|| {
let invalid_msg = "The provided mouse button code is invalid";
let revert_msg = "Reverting to the default button.";
WARNING!("{invalid_msg} ({code}). {revert_msg}");
default()
})
}
/// The code point of the button.

View File

@ -5,7 +5,9 @@ use crate::*;
// === Export ===
// ==============
pub use enso_shapely::entry_point;
pub use enso_shapely::CloneRef;
pub use enso_shapely::NoCloneBecauseOfCustomDrop;

View File

@ -438,3 +438,14 @@ impl<T: ?Sized> WeakRef for Weak<T> {
Weak::upgrade(self)
}
}
// ======================
// === ImplementsDrop ===
// ======================
/// Check whether the structure implements custom drop behavior. Used mainly by the
/// [`NoCloneBecauseOfCustomDrop`] macro.
#[allow(drop_bounds)]
pub trait ImplementsDrop: Drop {}

View File

@ -0,0 +1,41 @@
use crate::prelude::*;
// ===================
// === Entry Point ===
// ===================
fn crate_name_to_fn_name(name: &str) -> String {
let name = name.replace("ensogl-example-", "");
let name = name.replace("enso-example-", "");
let name = name.replace("enso-", "");
let name = name.replace("example-", "");
let name = name.replace('-', "_");
format!("entry_point_{}", name)
}
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
let decl = syn::parse_macro_input!(input as syn::Item);
match decl {
syn::Item::Fn(f) => {
let name = f.sig.ident.to_string();
if &name != "main" {
panic!("The function should be named 'main'.");
}
let fn_name = quote::format_ident!("{}", crate_name_to_fn_name(&crate_name));
let mut fn_sig = f.sig.clone();
fn_sig.ident = fn_name;
let attrs = &f.attrs;
let block = &f.block;
let output = quote! {
#(#attrs)*
#[wasm_bindgen]
pub #fn_sig #block
};
output.into()
}
_ => panic!("This macro is intended to be used on functions only."),
}
}

View File

@ -0,0 +1,22 @@
use crate::prelude::*;
use syn::DeriveInput;
// ===================
// === Entry Point ===
// ===================
/// Makes sure that the structure does not derive [`Clone`] and that it implements custom [`Drop`]
/// implementation.
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let decl = syn::parse_macro_input!(input as DeriveInput);
let ident = &decl.ident;
let (impl_generics, ty_generics, _) = &decl.generics.split_for_impl();
let output = quote! {
impl #impl_generics !Clone for #ident #ty_generics {}
impl #impl_generics ImplementsDrop for #ident #ty_generics {}
};
output.into()
}

View File

@ -22,7 +22,9 @@
extern crate proc_macro;
mod derive_clone_ref;
mod derive_entry_point;
mod derive_iterator;
mod derive_no_clone;
mod overlappable;
mod prelude {
@ -91,6 +93,36 @@ pub fn derive_clone_ref(input: proc_macro::TokenStream) -> proc_macro::TokenStre
derive_clone_ref::derive(input)
}
/// Makes sure that the structure does not derive [`Clone`] and that it implements custom [`Drop`]
/// implementation.
///
/// For the given input
/// ```ignore
/// #[derive(NoCloneBecauseOfCustomDrop)]
/// struct Test {}
/// ```
///
/// The following output will be generated:
/// ```ignore
/// struct Test {}
/// impl !Clone for Test {}
// impl ImplementsDrop for Test {}
/// ```
#[proc_macro_derive(NoCloneBecauseOfCustomDrop)]
pub fn derive_no_clone(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
derive_no_clone::derive(input)
}
/// Exposes the function as an application entry point. Entry points are alternative application
/// running modes that you can access by adding `?entry=` to the end of the application URL.
#[proc_macro_attribute]
pub fn entry_point(
_: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
derive_entry_point::derive(item)
}
#[allow(missing_docs)]
#[proc_macro_attribute]
pub fn overlappable(

View File

@ -378,3 +378,109 @@ macro_rules! _normalize_input {
$crate::_normalize_input! { $f $f_args [$($out)* $in] $($rest)* }
};
}
/// New version of [`shared`] - faster, understood by IntelliJ, but not yet covering all cases yet
/// (like where contexts in functions). These cases can be added in the future.
#[macro_export]
macro_rules! shared2 {
($name:ident
$(#$data_meta:tt)*
pub struct $data_name:ident $body:tt
impl {$(
$(#$fn_meta:tt)*
$fn_vis:vis fn $fn_name:ident $(<$($fn_param:ident : $fn_param_ty:ty),*>)?
($($fn_args:tt)*) $(-> $fn_out:ty)? { $($fn_body:tt)* }
)*}
) => {
$(#$data_meta)*
pub struct $data_name $body
#[derive(Clone, CloneRef)]
$(#$data_meta)*
pub struct $name {
rc: Rc<RefCell<$data_name>>
}
$(
$crate::shared_fn2!{ $name $data_name
[[$(#$fn_meta)*] $fn_vis [<$($($fn_param : ($fn_param_ty)),*)?>]] $fn_name
($($fn_args)*) ($($fn_args)*) [-> ($($fn_out)?)] { $($fn_body)* }
}
)*
};
}
#[macro_export]
macro_rules! shared_fn2 {
($name:ident $data_name:ident
$fn_sig:tt $fn_name:ident
(&self $(,$($fn_arg:ident : $fn_arg_ty:ty),*)?) $fn_all_args:tt $fn_out:tt $fn_body:tt
) => {
impl $data_name {
$crate::shared_fn_flatten2! { $fn_sig $fn_name $fn_all_args $fn_out $fn_body }
}
impl $name {
$crate::shared_fn_flatten2! {
$fn_sig $fn_name (&self $(,$($fn_arg : $fn_arg_ty),*)?) $fn_out {
self.rc.borrow().$fn_name($($($fn_arg),*)?)
}
}
}
};
($name:ident $data_name:ident
$fn_sig:tt $fn_name:ident
(&mut self $(,$($fn_arg:ident : $fn_arg_ty:ty),*)?) $fn_all_args:tt $fn_out:tt $fn_body:tt
) => {
impl $data_name {
$crate::shared_fn_flatten2! { $fn_sig $fn_name $fn_all_args $fn_out $fn_body }
}
impl $name {
$crate::shared_fn_flatten2! {
$fn_sig $fn_name (&self $(,$($fn_arg : $fn_arg_ty),*)?) $fn_out {
self.rc.borrow_mut().$fn_name($($($fn_arg),*)?)
}
}
}
};
($name:ident $data_name:ident
$fn_sig:tt new ($($fn_arg:ident : $fn_arg_ty:ty),*) $fn_all_args:tt $fn_out:tt $fn_body:tt
) => {
impl $data_name {
$crate::shared_fn_flatten2! { $fn_sig new $fn_all_args $fn_out $fn_body }
}
impl $name {
$crate::shared_fn_flatten2! { $fn_sig new $fn_all_args $fn_out {
Self { rc: Rc::new(RefCell::new($data_name::new ($($fn_arg),*))) }
} }
}
};
($name:ident $data_name:ident
$fn_sig:tt $fn_name:ident ($($fn_args2:tt)*) $fn_all_args:tt $fn_out:tt $fn_body:tt
) => {
impl $data_name {
$crate::shared_fn_flatten2! { $fn_sig $fn_name $fn_all_args $fn_out $fn_body }
}
};
}
#[macro_export]
macro_rules! shared_fn_flatten2 {
(
[ [$($fn_meta:tt)*] $fn_vis:vis [$($fn_params:tt)*] ]
$fn_name:ident ($($fn_args:tt)*) [-> $fn_out:tt] {
$($fn_body:tt)*
}
) => {
$($fn_meta)*
$fn_vis fn $fn_name $($fn_params)* ($($fn_args)*) -> $fn_out { $($fn_body)* }
};
}

View File

@ -316,6 +316,7 @@ where Self: MockData + MockDefault + AsRef<JsValue> + Into<JsValue> {
mock_data! { JsValue
fn is_undefined(&self) -> bool;
fn is_null(&self) -> bool;
}
impl JsValue {
@ -392,7 +393,9 @@ impl From<&JsString> for String {
// === Array ===
mock_data! { Array => Object }
mock_data! { Array => Object
fn length(&self) -> u32;
}
// === Error ===

View File

@ -359,6 +359,14 @@ ops! { ReflectOps for Reflect
/// [`get_nested`] to learn more.
fn get_nested_object(target: &JsValue, keys: &[&str]) -> Result<Object, JsValue>;
/// Get the nested value of the provided object. In case the object does not exist, they
/// will be created. See docs of [`get_nested`] to learn more.
fn get_nested_or_create(target: &JsValue, keys: &[&str]) -> Result<JsValue, JsValue>;
/// Get the nested value of the provided object and cast it to [`Object`]. In case the
/// object does not exist, they will be created. See docs of [`get_nested`] to learn more.
fn get_nested_object_or_create(target: &JsValue, keys: &[&str]) -> Result<Object, JsValue>;
/// Get the nested value of the provided object and cast it to [`String`]. See docs of
/// [`get_nested`] to learn more.
fn get_nested_object_printed_as_string(target: &JsValue, keys: &[&str])
@ -381,6 +389,35 @@ ops! { ReflectOps for Reflect
tgt.dyn_into()
}
fn get_nested_or_create
(target: &JsValue, keys: &[&str]) -> Result<JsValue, JsValue> {
let mut tgt = target.clone();
for key in keys {
let obj = tgt.dyn_into::<Object>()?;
let key = (*key).into();
match Reflect::get(&obj, &key) {
Ok(v) => {
if v.is_undefined() || v.is_null() {
tgt = Object::new().into();
Reflect::set(&obj, &key, &tgt)?;
} else {
tgt = v;
}
}
Err(_) => {
tgt = Object::new().into();
Reflect::set(&obj, &key, &tgt)?;
}
}
}
Ok(tgt)
}
fn get_nested_object_or_create(target: &JsValue, keys: &[&str]) -> Result<Object, JsValue> {
let tgt = Self::get_nested_or_create(target, keys)?;
tgt.dyn_into()
}
fn get_nested_object_printed_as_string
(target: &JsValue, keys: &[&str]) -> Result<String, JsValue> {
let tgt = Self::get_nested(target, keys)?;

2
run
View File

@ -7,7 +7,7 @@ const os = require('os')
const fss = require('fs')
const paths = require('./build/paths')
process.on('unhandledRejection', error => { throw(error) })
process.on('unhandledRejection', error => { throw(error.stack) })
let args = process.argv.slice(2)