mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 22:10:15 +03:00
Allowing EnsoGL mouse to interact with more than 4096 sprites at the same time. (#3351)
This commit is contained in:
parent
20be5516a5
commit
546c333269
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -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",
|
||||
|
@ -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(¬_selected);
|
||||
@ -280,7 +281,7 @@ impl ProjectName {
|
||||
);
|
||||
on_deselect <- not_selected.gate(¬_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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ===
|
||||
|
@ -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 ===
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -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 ===
|
||||
// =============
|
||||
|
@ -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();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
168
lib/rust/ensogl/core/src/display/scene/pointer_target.rs
Normal file
168
lib/rust/ensogl/core/src/display/scene/pointer_target.rs
Normal 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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -0,0 +1 @@
|
||||
100
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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`
|
||||
|
@ -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 }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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" }
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
|
15
lib/rust/ensogl/example/custom-shape-system/Cargo.toml
Normal file
15
lib/rust/ensogl/example/custom-shape-system/Cargo.toml
Normal 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"}
|
76
lib/rust/ensogl/example/custom-shape-system/src/lib.rs
Normal file
76
lib/rust/ensogl/example/custom-shape-system/src/lib.rs
Normal 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();
|
||||
}
|
@ -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();
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
|
@ -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")));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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"}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -5,7 +5,9 @@ use crate::*;
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub use enso_shapely::entry_point;
|
||||
pub use enso_shapely::CloneRef;
|
||||
pub use enso_shapely::NoCloneBecauseOfCustomDrop;
|
||||
|
||||
|
||||
|
||||
|
@ -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 {}
|
||||
|
41
lib/rust/shapely/macros/src/derive_entry_point.rs
Normal file
41
lib/rust/shapely/macros/src/derive_entry_point.rs
Normal 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."),
|
||||
}
|
||||
}
|
22
lib/rust/shapely/macros/src/derive_no_clone.rs
Normal file
22
lib/rust/shapely/macros/src/derive_no_clone.rs
Normal 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()
|
||||
}
|
@ -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(
|
||||
|
@ -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)* }
|
||||
};
|
||||
}
|
||||
|
@ -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 ===
|
||||
|
@ -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)?;
|
||||
|
Loading…
Reference in New Issue
Block a user