diff --git a/Cargo.lock b/Cargo.lock index d1b1ef04b395..bde01b80ce76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2883,6 +2883,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ensogl-example-list-editor" +version = "0.1.0" +dependencies = [ + "enso-frp", + "ensogl-core", + "ensogl-list-editor", + "ensogl-slider", + "ensogl-text-msdf", +] + [[package]] name = "ensogl-example-list-view" version = "0.1.0" @@ -3008,15 +3019,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "ensogl-example-vector-editor" -version = "0.1.0" -dependencies = [ - "ensogl-core", - "ensogl-hardcoded-theme", - "wasm-bindgen", -] - [[package]] name = "ensogl-examples" version = "0.1.0" @@ -3034,6 +3036,7 @@ dependencies = [ "ensogl-example-focus-management", "ensogl-example-grid-view", "ensogl-example-instance-ordering", + "ensogl-example-list-editor", "ensogl-example-list-view", "ensogl-example-mouse-events", "ensogl-example-profiling-run-graph", @@ -3043,7 +3046,6 @@ dependencies = [ "ensogl-example-sprite-system", "ensogl-example-sprite-system-benchmark", "ensogl-example-text-area", - "ensogl-example-vector-editor", ] [[package]] diff --git a/lib/rust/ensogl/component/list-editor/src/lib.rs b/lib/rust/ensogl/component/list-editor/src/lib.rs index a8ed5176e557..07c96aab0b4c 100644 --- a/lib/rust/ensogl/component/list-editor/src/lib.rs +++ b/lib/rust/ensogl/component/list-editor/src/lib.rs @@ -73,15 +73,11 @@ #![allow(clippy::bool_to_int_with_if)] #![allow(clippy::let_and_return)] -use ensogl_core::display::shape::compound::rectangle::*; use ensogl_core::display::world::*; use ensogl_core::prelude::*; -use ensogl_core::application::Application; use ensogl_core::control::io::mouse; -use ensogl_core::data::color; use ensogl_core::display; -use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::object::Event; use ensogl_core::display::object::ObjectOps; use ensogl_core::gui::cursor; @@ -262,30 +258,50 @@ impl From for ItemOrPlaceholder { // === ListEditor === // ================== -ensogl_core::define_endpoints_2! { +ensogl_core::define_endpoints_2! { Input { /// Push a new element to the end of the list. - push(Weak), + push(Rc>>), - insert((Index, Weak)), + /// Insert a new element in the given position. If the index is bigger than the list length, + /// the item will be placed at the end of the list. + insert((Index, Rc>>)), /// Remove the element at the given index. If the index is invalid, nothing will happen. remove(Index), + /// Set the spacing between elements. gap(f32), + /// The distance the user needs to drag the element along secondary axis to start dragging + /// the element. See docs of this module to learn more. secondary_axis_drag_threshold(f32), + + /// The distance the user needs to drag the element along primary axis to consider it not a + /// drag movement and thus to pass mouse events to the item. See docs of this module to + /// learn more. primary_axis_no_drag_threshold(f32), + + /// The time in which the `primary_axis_no_drag_threshold` drops to zero. primary_axis_no_drag_threshold_decay_time(f32), + + /// Controls the distance an item needs to be dragged out of the list for it to be trashed. + /// See docs of this module to learn more. thrashing_offset_ratio(f32), + + /// Enable insertion points (plus icons) when moving mouse next to any of the list items. enable_all_insertion_points(bool), + + /// Enable insertion points (plus icons) when moving mouse after the last list item. enable_last_insertion_point(bool), } Output { /// Fires whenever a new element was added to the list. - on_item_added(Response<(Index, Weak)>), + on_item_added(Response), - on_item_removed(Response<(Index, Weak)>), + /// Fires whenever an element was removed from the list. This can happen when dragging the + /// element to switch its position. + on_item_removed(Response<(Index, Rc>>)>), /// Request new item to be inserted at the provided index. In most cases, this happens after /// clicking a "plus" icon to add new element to the list. As a response, you should use the @@ -296,7 +312,7 @@ ensogl_core::define_endpoints_2! { #[derive(Derivative, CloneRef, Debug, Deref)] #[derivative(Clone(bound = ""))] -pub struct ListEditor { +pub struct ListEditor { #[deref] pub frp: Frp, root: display::object::Instance, @@ -339,7 +355,7 @@ impl From> for SharedModel { } -impl ListEditor { +impl ListEditor { pub fn new(cursor: &Cursor) -> Self { let frp = Frp::new(); let model = Model::new(cursor); @@ -359,10 +375,6 @@ impl ListEditor { let on_move = scene.on_event::(); frp::extend! { network - - // Do not pass events to children, as we don't know whether we are about to drag - // them yet. - eval on_down ([] (event) event.stop_propagation()); target <= on_down.map(|event| event.target()); on_up <- on_up_source.identity(); @@ -385,13 +397,21 @@ impl ListEditor { } self.init_add_and_remove(); - let (is_dragging, drag_diff) = self.init_dragging(&on_up, &on_down, &target, &pos_diff); + let (is_dragging, drag_diff, no_drag) = + self.init_dragging(&on_up, &on_down, &target, &pos_diff); let (is_trashing, trash_pointer_style) = self.init_trashing(&on_up, &drag_diff); self.init_dropping(&on_up, &pos_on_move_down, &is_trashing); let insert_pointer_style = self.init_insertion_points(&on_up, &pos_on_move, &is_dragging); frp::extend! { network cursor.frp.set_style_override <+ all [insert_pointer_style, trash_pointer_style].fold(); + on_down_drag <- on_down.gate_not(&no_drag); + // Do not pass events to children, as we don't know whether we are about to drag + // them yet. + eval on_down_drag ([] (event) event.stop_propagation()); + _eval <- no_drag.on_true().map3(&on_down, &target, |_, event, target| { + target.emit_event(event.payload.clone()); + }); } self } @@ -451,18 +471,18 @@ impl ListEditor { let network = self.frp.network(); frp::extend! { network - push_ix <= frp.push.map(f!((item) model.push_weak(item))); - on_pushed <- frp.push.map2(&push_ix, |t, ix| Response::api((*ix, t.clone()))); + push_ix <= frp.push.map(f!((item) model.push_cell(item))); + on_pushed <- push_ix.map(|ix| Response::api(*ix)); frp.private.output.on_item_added <+ on_pushed; - insert_ix <= frp.insert.map(f!(((index, item)) model.insert_weak(*index, item))); - on_inserted <- frp.insert.map2(&insert_ix, |t, ix| Response::api((*ix, t.1.clone()))); + insert_ix <= frp.insert.map(f!(((index, item)) model.insert_cell(*index, item))); + on_inserted <- insert_ix.map(|ix| Response::api(*ix)); frp.private.output.on_item_added <+ on_inserted; let on_item_removed = &frp.private.output.on_item_removed; eval frp.remove([model, on_item_removed] (index) { if let Some(item) = model.borrow_mut().trash_item_at(*index) { - on_item_removed.emit(Response::api((*index, Rc::new(item).downgrade()))); + on_item_removed.emit(Response::api((*index, Rc::new(RefCell::new(Some(item)))))); } }); } @@ -475,7 +495,7 @@ impl ListEditor { on_down: &frp::Stream>, target: &frp::Stream, pos_diff: &frp::Stream, - ) -> (frp::Stream, frp::Stream) { + ) -> (frp::Stream, frp::Stream, frp::Stream) { let model = &self.model; let on_up = on_up.clone_ref(); let on_down = on_down.clone_ref(); @@ -499,6 +519,7 @@ impl ListEditor { init_drag_not_disabled <- init_drag.gate_not(&drag_disabled); is_dragging <- bool(&on_up, &init_drag_not_disabled).on_change(); drag_diff <- pos_diff.gate(&is_dragging); + no_drag <- drag_disabled.gate_not(&is_dragging).on_change(); status <- bool(&on_up, &drag_diff).on_change(); start <- status.on_true(); @@ -506,11 +527,11 @@ impl ListEditor { let on_item_removed = &frp.private.output.on_item_removed; eval target_on_start([model, on_item_removed] (t) { if let Some((index, item)) = model.borrow_mut().start_item_drag(t) { - on_item_removed.emit(Response::gui((index, Rc::new(item).downgrade()))); + on_item_removed.emit(Response::gui((index, Rc::new(RefCell::new(Some(item)))))); } }); } - (status, drag_diff) + (status, drag_diff, no_drag) } /// Implementation of item trashing logic. See docs of this crate to learn more. @@ -571,8 +592,8 @@ impl ListEditor { let on_item_added = &frp.private.output.on_item_added; eval insert_index_on_drop ([model, on_item_added] (index) - if let Some((index, item)) = model.borrow_mut().place_dragged_item(*index) { - on_item_added.emit(Response::gui((index, Rc::new(item).downgrade()))); + if let Some(index) = model.borrow_mut().place_dragged_item(*index) { + on_item_added.emit(Response::gui(index)); } ); } @@ -591,7 +612,7 @@ impl ListEditor { } pub fn push(&self, item: T) { - self.frp.push(Rc::new(item).downgrade()); + self.frp.push(Rc::new(RefCell::new(Some(item)))); } pub fn items(&self) -> Vec { @@ -612,16 +633,18 @@ impl SharedModel { self.borrow_mut().push(item) } - fn push_weak(&self, item: &Weak) -> Option { - item.upgrade().map(|item| self.push((*item).clone_ref())) + fn push_cell(&self, item: &Rc>>) -> Option { + let item = mem::take(&mut *item.borrow_mut()); + item.map(|item| self.push(item)) } fn insert(&self, index: Index, item: T) -> Index { self.borrow_mut().insert(index, item) } - fn insert_weak(&self, index: Index, item: &Weak) -> Option { - item.upgrade().map(|item| self.insert(index, (*item).clone_ref())) + fn insert_cell(&self, index: Index, item: &Rc>>) -> Option { + let item = mem::take(&mut *item.borrow_mut()); + item.map(|item| self.insert(index, item)) } fn insert_index(&self, x: f32, center_points: &[f32]) -> ItemOrPlaceholderIndex { @@ -681,7 +704,7 @@ impl Model { /// Find an element by the provided display object reference. fn item_index_of( - &mut self, + &self, obj: &display::object::Instance, ) -> Option<(Index, ItemOrPlaceholderIndex)> { self.items @@ -841,11 +864,12 @@ impl Model { /// /// See docs of [`Self::start_item_drag_at`] for more information. fn start_item_drag(&mut self, target: &display::object::Instance) -> Option<(Index, T)> { - let index = self.item_index_of(target); - if let Some((index, index_or_placeholder_index)) = index { + let objs = target.rev_parent_chain(); + let tarrget_index = objs.into_iter().find_map(|t| self.item_index_of(&t)); + if let Some((index, index_or_placeholder_index)) = tarrget_index { self.start_item_drag_at(index_or_placeholder_index).map(|item| (index, item)) } else { - warn!("Requested to drag a non-existent item."); + warn!("Could not find the item to drag."); None } } @@ -941,7 +965,7 @@ impl Model { /// Place the currently dragged element in the given index. The item will be enclosed in the /// [`Item`] object, will handles its animation. See the documentation of /// [`ItemOrPlaceholder`] to learn more. - fn place_dragged_item(&mut self, index: ItemOrPlaceholderIndex) -> Option<(Index, T)> { + fn place_dragged_item(&mut self, index: ItemOrPlaceholderIndex) -> Option { if let Some(item) = self.cursor.stop_drag_if_is::() { self.collapse_all_placeholders_no_margin_update(); if let Some((index, placeholder)) = self.get_indexed_merged_placeholder_at(index) { @@ -958,7 +982,7 @@ impl Model { warn!("An element was inserted without a placeholder. This should not happen."); } self.reposition_items(); - self.item_or_placeholder_index_to_index(index).map(|index| (index, item)) + self.item_or_placeholder_index_to_index(index) } else { warn!("Called function to insert dragged element, but no element is being dragged."); None @@ -1036,7 +1060,7 @@ impl Model { } } -impl display::Object for ListEditor { +impl display::Object for ListEditor { fn display_object(&self) -> &display::object::Instance { &self.root } @@ -1092,92 +1116,3 @@ mod trash { } use crate::placeholder::WeakPlaceholder; use trash::Trash; - - -// =================== -// === Entry Point === -// =================== - -pub mod glob { - use super::*; - ensogl_core::define_endpoints_2! { - Input { - } - Output { - } - } -} - -/// The example entry point. -#[entry_point] -#[allow(dead_code)] -pub fn main() { - let app = Application::new("root"); - let world = app.display.clone(); - let scene = &world.default_scene; - - let camera = scene.camera().clone_ref(); - let navigator = Navigator::new(scene, &camera); - - let vector_editor = ListEditor::::new(&app.cursor); - - - let shape1 = Circle().build(|t| { - t.set_size(Vector2(60.0, 100.0)) - .set_color(color::Rgba::new(0.0, 0.0, 0.0, 0.1)) - .set_inset_border(2.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 0.0, 0.5)) - .keep_bottom_left_quarter(); - }); - let shape2 = RoundedRectangle(10.0).build(|t| { - t.set_size(Vector2(120.0, 100.0)) - .set_color(color::Rgba::new(0.0, 0.0, 0.0, 0.1)) - .set_inset_border(2.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 0.0, 0.5)); - }); - let shape3 = RoundedRectangle(10.0).build(|t| { - t.set_size(Vector2(240.0, 100.0)) - .set_color(color::Rgba::new(0.0, 0.0, 0.0, 0.1)) - .set_inset_border(2.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 0.0, 0.5)); - }); - - - let glob_frp = glob::Frp::new(); - let glob_frp_network = glob_frp.network(); - - let shape1_down = shape1.on_event::(); - frp::extend! { glob_frp_network - eval_ shape1_down ([] { - warn!("Shape 1 down"); - }); - new_item <- vector_editor.request_new_item.map(|_| { - let shape = RoundedRectangle(10.0).build(|t| { - t.set_size(Vector2(100.0, 100.0)) - .set_color(color::Rgba::new(0.0, 0.0, 0.0, 0.1)) - .set_inset_border(2.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 0.0, 0.5)); - }); - Rc::new(shape) - }); - vector_editor.insert <+ vector_editor.request_new_item.map2(&new_item, |index, item| - (**index, item.downgrade()) - ); - } - - vector_editor.push(shape1); - vector_editor.push(shape2); - vector_editor.push(shape3); - - let root = display::object::Instance::new(); - root.set_size(Vector2(300.0, 100.0)); - root.add_child(&vector_editor); - world.add_child(&root); - - world.keep_alive_forever(); - mem::forget(app); - mem::forget(glob_frp); - mem::forget(navigator); - mem::forget(root); - mem::forget(vector_editor); -} diff --git a/lib/rust/ensogl/component/slider/src/lib.rs b/lib/rust/ensogl/component/slider/src/lib.rs index f7b689677c86..731d9868c8e6 100644 --- a/lib/rust/ensogl/component/slider/src/lib.rs +++ b/lib/rust/ensogl/component/slider/src/lib.rs @@ -1,4 +1,11 @@ //! A slider UI component that allows adjusting a value through mouse interaction. +//! +//! # Important [WD] +//! Please note that the implementation is not finished yet. It was refactored to make the slider +//! implementation use the newest EnsoGL API, however, not all functionality was restored yet. As +//! this component is not used in the application yet, it is kept as is, but should be updated +//! before the real usage. In particualar, vertical sliders and sliders that behave as scrollbars +//! are not working correctly now. #![recursion_limit = "512"] // === Standard Linter Configuration === @@ -46,30 +53,30 @@ pub mod model; // === Constants === // ================= -/// Default slider precision when slider dragging is initiated. The precision indicates both how +/// Default slider resolution when slider dragging is initiated. The resolution indicates both how /// much the value is changed per pixel dragged and how many digits are displayed after the decimal. -const PRECISION_DEFAULT: f32 = 1.0; +const RESOLUTION_DEFAULT: f32 = 1.0; /// Default upper limit of the slider value. const MAX_VALUE_DEFAULT: f32 = 100.0; /// Default for the maximum number of digits after the decimal point that is displayed. const MAX_DISP_DECIMAL_PLACES_DEFAULT: usize = 8; /// Margin above/below the component within which vertical mouse movement will not affect slider -/// precision. +/// resolution. const PRECISION_ADJUSTMENT_MARGIN: f32 = 10.0; -/// The vertical mouse movement (in pixels) needed to change the slider precision by one step. -/// Dragging the mouse upward beyond the margin will decrease the precision by one step for every +/// The vertical mouse movement (in pixels) needed to change the slider resolution by one step. +/// Dragging the mouse upward beyond the margin will decrease the resolution by one step for every /// `STEP_SIZE` pixels and adjust the slider value more quickly. Dragging the mouse downwards will -/// increase the precision and change the value more slowly. +/// increase the resolution and change the value more slowly. const PRECISION_ADJUSTMENT_STEP_SIZE: f32 = 50.0; -/// The actual slider precision changes exponentially with each adjustment step. When the adjustment -/// is changed by one step, the slider's precision is changed to the next power of `STEP_BASE`. A -/// `STEP_BASE` of 10.0 results in the precision being powers of 10 for consecutive steps, e.g [1.0, -/// 10.0, 100.0, ...] when decreasing the precision and [0.1, 0.01, 0.001, ...] when increasing the -/// precision. +/// The actual slider resolution changes exponentially with each adjustment step. When the +/// adjustment is changed by one step, the slider's resolution is changed to the next power of +/// `STEP_BASE`. A `STEP_BASE` of 10.0 results in the resolution being powers of 10 for consecutive +/// steps, e.g [1.0, 10.0, 100.0, ...] when decreasing the resolution and [0.1, 0.01, 0.001, ...] +/// when increasing the resolution. const PRECISION_ADJUSTMENT_STEP_BASE: f32 = 10.0; -/// Limit the number of precision steps to prevent overflow or rounding to zero of the precision. +/// Limit the number of resolution steps to prevent overflow or rounding to zero of the resolution. const MAX_PRECISION_ADJUSTMENT_STEPS: usize = 8; -/// A pop-up is displayed whenever the slider's precision is changed. This is the duration for +/// A pop-up is displayed whenever the slider's resolution is changed. This is the duration for /// which the pop-up is visible. const PRECISION_ADJUSTMENT_POPUP_DURATION: f32 = 1000.0; /// The delay before an information tooltip is displayed when hovering over a slider component. @@ -100,37 +107,20 @@ pub enum LabelPosition { -// ========================== -// === Slider orientation === -// ========================== +// ================== +// === DragHandle === +// ================== -// /// The orientation of the slider component. -// #[derive(Clone, Copy, Debug, Default)] -// pub enum Axis2 { -// #[default] -// /// The slider value is changed by dragging the slider horizontally. -// Horizontal, -// /// The slider value is changed by dragging the slider vertically. -// Vertical, -// } - - - -// ================================= -// === Slider position indicator === -// ================================= - -/// The type of element that indicates the slider's value along its length. -#[derive(Clone, Copy, Debug, Default)] -pub enum Kind { +/// Defines which part of the slider is being dragged by the user. In case the slider allows +/// dragging both of its ends and the middle of the track, this struct determines which part is +/// being dragged. +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone, Default)] +pub enum DragHandle { + Start, + Middle, #[default] - /// A track is a bar that fills the slider as the value increases. The track is empty when the - /// slider's value is at the lower limit and filled when the value is at the upper limit. - SingleValue, - /// A thumb is a small element that moves across the slider as the value changes. The thumb is - /// on the left/lower end of the slider when the slider's value is at the lower limit and on - /// the right/upper end of the slider when the value is at the upper limit. - Scrollbar(f32), + End, } @@ -225,16 +215,16 @@ fn value_limit_clamp( ensogl_core::define_endpoints_2! { Input { - // /// Set the width of the slider component. - // set_width(f32), - // /// Set the height of the slider component. - // set_height(f32), - /// Set the type of the slider's value indicator. - kind(Kind), /// Set the color of the slider's value indicator. set_value_indicator_color(color::Lcha), /// Set the color of the slider's background. set_background_color(color::Lcha), + /// Allow dragging the start point of sliders track. + enable_start_track_drag(bool), + /// Allow dragging the end point of sliders track. + enable_end_track_drag(bool), + /// Allow dragging the sliders track by pressing in the middle of it. + enable_middle_track_drag(bool), /// Set the slider value. set_value(f32), /// Set the default value to reset a slider to when `ctrl` + `click`-ed. @@ -249,22 +239,22 @@ ensogl_core::define_endpoints_2! { set_value_text_color(color::Lcha), /// Set whether the slider's value text is hidden. show_value(bool), - /// Set the default precision at which the slider operates. The slider's precision + /// Set the default resolution at which the slider operates. The slider's resolution /// determines by what increment the value will be changed on mouse movement. It also /// affects the number of digits after the decimal point displayed. - set_default_precision(f32), - /// The slider's precision can be adjusted by dragging the mouse in the vertical direction. + set_default_resolution(f32), + /// The slider's resolution can be adjusted by dragging the mouse in the vertical direction. /// The `adjustment_margin` defines a margin above/below the slider within which no - /// precision adjustment will be performed. + /// resolution adjustment will be performed. set_precision_adjustment_margin(f32), - /// The slider's precision can be adjusted by dragging the mouse in the vertical direction. + /// The slider's resolution can be adjusted by dragging the mouse in the vertical direction. /// The `adjustment_step_size` defines the distance the mouse must be moved to increase or - /// decrease the precision by one step. + /// decrease the resolution by one step. set_precision_adjustment_step_size(f32), - /// Set the maximum number of precision steps to prevent overflow or rounding to zero of the - /// precision increments. + /// Set the maximum number of resolution steps to prevent overflow or rounding to zero of the + /// resolution increments. set_max_precision_adjustment_steps(usize), - /// Set whether the precision adjustment mechansim is disabled. + /// Set whether the resolution adjustment mechansim is disabled. set_precision_adjustment_disabled(bool), /// Set the slider's label. The label will be displayed to the left of the slider's value /// display. @@ -281,7 +271,7 @@ ensogl_core::define_endpoints_2! { set_tooltip(ImString), /// Set the delay of the tooltip showing after the mouse hovers over the component. set_tooltip_delay(f32), - /// A pop-up is displayed whenever the slider's precision is changed. This is the duration + /// A pop-up is displayed whenever the slider's resolution is changed. This is the duration /// for which the pop-up is visible. set_precision_popup_duration(f32), /// Set whether the slider is disabled. When disabled, the slider's value cannot be changed @@ -308,10 +298,12 @@ ensogl_core::define_endpoints_2! { width(f32), /// The component's height. height(f32), - /// The slider's value. - value(f32), - /// The slider's precision. - precision(f32), + /// The slider track's start position. + start_value(f32), + /// The slider track's end position. + end_value(f32), + /// The slider's resolution. + resolution(f32), /// The slider value's lower limit. This takes into account limit extension if an adaptive /// slider limit is set. min_value(f32), @@ -319,15 +311,11 @@ ensogl_core::define_endpoints_2! { /// slider limit is set. max_value(f32), /// Indicates whether the mouse is currently hovered over the component. - hovered(bool), - /// Indicates whether the slider is currently being dragged. dragged(bool), /// Indicates whether the slider is disabled. disabled(bool), /// Indicates whether the slider's value is being edited currently. editing(bool), - // /// The orientation of the slider, either horizontal or vertical. - // orientation(Axis2), } } @@ -341,9 +329,9 @@ ensogl_core::define_endpoints_2! { /// slider in a horizontal direction changes the value, limited to a range between `min_value` and /// `max_value`. The selected value is displayed, and a track fills the slider proportional to the /// value within the specified range. Dragging the slider in a vertical direction adjusts the -/// precision of the slider. The precision affects the increments by which the value changes when +/// resolution of the slider. The resolution affects the increments by which the value changes when /// the mouse is moved. -#[derive(Debug, Deref, Clone)] +#[derive(Debug, Deref, Clone, CloneRef)] pub struct Slider { /// Public FRP api of the component. #[deref] @@ -391,18 +379,21 @@ impl Slider { let ptr_down_any = model.background.on_event::(); let ptr_up_any = scene.on_event::(); - let ptr_out = model.background.on_event::(); - let ptr_over = model.background.on_event::(); - let obj = model.display_object(); frp::extend! { network ptr_down <- ptr_down_any.map(|e| e.button() == mouse::PrimaryButton).on_true(); ptr_up <- ptr_up_any.map(|e| e.button() == mouse::PrimaryButton).on_true(); pos <- mouse.position.map( - f!([scene, model] (p) scene.screen_to_object_space(&model.background, *p)) + f!([scene, model] (p) scene.screen_to_object_space(model.display_object(), *p)) ); - value_on_ptr_down <- output.value.sample(&ptr_down); + + orientation_orth <- frp.orientation.map(|o| o.orthogonal()); + length <- all_with(&obj.on_resized, &frp.orientation, |size, dim| size.get_dim(dim)); + width <- all_with(&obj.on_resized, &orientation_orth, |size, dim| size.get_dim(dim)); + + start_value_on_ptr_down <- output.start_value.sample(&ptr_down); + end_value_on_ptr_down <- output.end_value.sample(&ptr_down); ptr_down <- ptr_down.gate_not(&frp.set_slider_disabled); ptr_down <- ptr_down.gate_not(&output.editing); @@ -410,35 +401,65 @@ impl Slider { on_editing <- output.editing.on_true(); on_drag_start <- ptr_down.gate_not(&keyboard.is_control_down); on_drag_stop <- any3(&ptr_up, &on_disabled, &on_editing); - dragging <- bool(&on_drag_stop, &on_drag_start); + output.dragged <+ bool(&on_drag_stop, &on_drag_start); drag_start <- pos.sample(&on_drag_start); - drag_end <- pos.gate(&dragging).any2(&drag_start); + drag_end <- pos.gate(&output.dragged).any2(&drag_start); drag_delta <- all2(&drag_end, &drag_start).map(|(end, start)| end - start); drag_delta1 <- all_with(&drag_delta, &frp.orientation, |t, d| t.get_dim(d)).on_change(); - orientation_orth <- frp.orientation.map(|o| o.orthogonal()); prec_delta <- all_with(&drag_end, &orientation_orth, |t, d| t.get_dim(d)).on_change(); - output.hovered <+ bool(&ptr_out, &ptr_over); - output.dragged <+ dragging; + handle <- drag_start.map9( + &length, + &start_value_on_ptr_down, + &end_value_on_ptr_down, + &output.min_value, + &output.max_value, + &frp.enable_start_track_drag, + &frp.enable_middle_track_drag, + &frp.enable_end_track_drag, + |pos, length, start, end, min, max, enable_start, enable_middle, enable_end| { + match (enable_start, enable_middle, enable_end) { + (false, false, false) => None, + (true, false, false) => Some(DragHandle::Start), + (false, true, false) => Some(DragHandle::Middle), + (false, false, true) => Some(DragHandle::End), + (true, true, false) => { + let val_range = max - min; + let start_pos = start / val_range * length; + if pos.x < start_pos { Some(DragHandle::Start) } + else { Some(DragHandle::Middle) } + } + (true, false, true) => { + let val_range = max - min; + let mid_pos = (start + end) / 2.0 / val_range * length; + if pos.x < mid_pos { Some(DragHandle::Start) } + else { Some(DragHandle::End) } + } + (false, true, true) => { + let val_range = max - min; + let end_pos = end / val_range * length; + if pos.x < end_pos { Some(DragHandle::Middle) } + else { Some(DragHandle::End) } + } + (true, true, true) => { + let val_range = max - min; + let start_pos = start / val_range * length; + let end_pos = end / val_range * length; + if pos.x < start_pos { Some(DragHandle::Start) } + else if pos.x > end_pos { Some(DragHandle::End) } + else { Some(DragHandle::Middle) } + } + } + } + ); // === Precision calculation === - length <- all_with(&obj.on_resized, &frp.orientation, |size, dim| size.get_dim(dim)); - width <- all_with(&obj.on_resized, &orientation_orth, |size, dim| size.get_dim(dim)); - - empty_space <- all_with3(&length, &frp.kind, &frp.set_thumb_size, - |length, indicator, _thumb_size| - match indicator { - Kind::Scrollbar(thumb_size) => length * (1.0 - thumb_size), - Kind::SingleValue => *length, - } + native_resolution <- all_with3(&length, &output.max_value, &output.min_value, + |len, max, min| (max - min) / len ); - - slider_range <- all_with(&output.max_value, &output.min_value, |max, min| *max - *min); - native_precision <- all2(&empty_space, &slider_range).map(|(l, r)| r / l); - - non_native_precision <- all_with5( + non_native_resolution <- all_with5( &width, &frp.set_precision_adjustment_margin, &prec_delta, @@ -451,39 +472,57 @@ impl Slider { let level = min(*max_steps as i32, (offset / step_size).ceil() as i32) * sign; (level != 0).as_some_from(|| { let exp = if level > 0 { level - 1 } else { level }; - let precision = 10.0_f32.powf(exp as f32); - precision + 10.0_f32.powf(exp as f32) }) } ).on_change(); - precision <- all_with(&non_native_precision, &native_precision, |t,s| t.unwrap_or(*s)); - output.precision <+ precision; + resolution <- all_with(&non_native_resolution, &native_resolution, |t,s| t.unwrap_or(*s)); + output.resolution <+ resolution; // === Value calculation === - value <- drag_delta1.map3(&value_on_ptr_down, &precision, - |delta, value, precision| value + delta * precision); - value <- any2(&frp.set_value, &value); - value <- all5( - &value, - &frp.set_min_value, - &frp.set_max_value, - &frp.set_lower_limit_type, - &frp.set_upper_limit_type, - ).map(value_limit_clamp); - output.value <+ value; + values <- drag_delta1.map5( + &handle, + &start_value_on_ptr_down, + &end_value_on_ptr_down, + &resolution, + |delta, handle, start_value, end_value, resolution| { + let diff = delta * resolution; + if let Some(handle) = handle { + match handle { + DragHandle::Start => (Some(start_value + diff), None), + DragHandle::End => (None, Some(end_value + diff)), + DragHandle::Middle => (Some(start_value + diff), Some(end_value + diff)) + } + } else { + (None, None) + } + }); + start_value <= values._0(); + end_value <= values._1(); + value <- any2(&frp.set_value, &end_value); + // value <- all5( + // &value, + // &frp.set_min_value, + // &frp.set_max_value, + // &frp.set_lower_limit_type, + // &frp.set_upper_limit_type, + // ).map(value_limit_clamp); + output.start_value <+ start_value; + output.end_value <+ value; // === Value Reset === reset_value <- ptr_down.gate(&keyboard.is_control_down); value_on_reset <- input.set_default_value.sample(&reset_value); - output.value <+ value_on_reset; + output.end_value <+ value_on_reset; // === Value Animation === - model.value_animation.target <+ output.value; + model.start_value_animation.target <+ output.start_value; + model.end_value_animation.target <+ output.end_value; }; } @@ -496,7 +535,7 @@ impl Slider { frp::extend! { network min_value <- all_with5( - &output.value, + &output.end_value, &input.set_min_value, &input.set_max_value, &output.min_value, @@ -506,7 +545,7 @@ impl Slider { output.min_value <+ min_value; max_value <- all_with5( - &output.value, + &output.end_value, &input.set_min_value, &input.set_max_value, &output.max_value, @@ -515,8 +554,8 @@ impl Slider { ).on_change(); output.max_value <+ max_value; - overflow_lower <- all_with(&output.value, &min_value, |v, min| v < min).on_change(); - overflow_upper <- all_with(&output.value, &max_value, |v, max| v > max).on_change(); + overflow_lower <- all_with(&output.end_value, &min_value, |v, min| v < min).on_change(); + overflow_upper <- all_with(&output.end_value, &max_value, |v, max| v > max).on_change(); eval overflow_lower((v) model.set_overflow_lower_visible(*v)); eval overflow_upper((v) model.set_overflow_upper_visible(*v)); }; @@ -533,15 +572,15 @@ impl Slider { frp::extend! { network eval input.show_value((v) model.show_value(*v)); - value <- output.value.sampled_gate(&input.show_value); + value <- output.end_value.sampled_gate(&input.show_value); default_value <- input.set_default_value.sampled_gate(&input.show_value); is_default <- all_with(&value, &default_value, |val, def| val == def); text_weight <- switch_constant(&is_default, Weight::Bold, Weight::Normal); eval text_weight ((v) model.set_value_text_property(*v)); - precision <- output.precision.sampled_gate(&input.show_value); + resolution <- output.resolution.sampled_gate(&input.show_value); max_decimal_places <- input.set_max_disp_decimal_places.sampled_gate(&input.show_value); - text <- all_with3(&value, &precision, &max_decimal_places, display_value); + text <- all_with3(&value, &resolution, &max_decimal_places, display_value); text_left <- text._0(); text_right <- text._1(); model.value_text_left.set_content <+ text_left; @@ -552,7 +591,7 @@ impl Slider { }; } - /// Initialize the precision pop-up FRP network. + /// Initialize the resolution pop-up FRP network. fn init_precision_popup(&self) { let network = self.frp.network(); let input = &self.frp.input; @@ -567,16 +606,16 @@ impl Slider { &component_events.mouse_release_primary, &component_events.mouse_down_primary ); - precision <- output.precision.on_change().gate(&component_drag); - model.tooltip.frp.set_style <+ precision.map(|precision| { + resolution <- output.resolution.on_change().gate(&component_drag); + model.tooltip.frp.set_style <+ resolution.map(|resolution| { let prec_text = format!( - "Precision: {precision:.MAX_DISP_DECIMAL_PLACES_DEFAULT$}", + "Precision: {resolution:.MAX_DISP_DECIMAL_PLACES_DEFAULT$}", ); let prec_text = prec_text.trim_end_matches('0'); let prec_text = prec_text.trim_end_matches('.'); tooltip::Style::set_label(prec_text.into()) }); - precision_changed <- precision.constant(()); + precision_changed <- resolution.constant(()); popup_anim.reset <+ precision_changed; popup_anim.start <+ precision_changed; popup_hide <- any2(&popup_anim.on_end, &component_events.mouse_release_primary); @@ -629,25 +668,28 @@ impl Slider { let obj = model.display_object(); frp::extend! { network - // comp_size <- all2(&input.set_width, &input.set_height).map(|(w, h)| Vector2(*w,*h)); eval obj.on_resized((size) model.update_size(*size)); - eval input.kind((i) model.kind(i)); - // output.width <+ input.set_width; - // output.height <+ input.set_height; min_limit_anim.target <+ output.min_value; max_limit_anim.target <+ output.max_value; - indicator_pos <- all3(&model.value_animation.value, &min_limit_anim.value, &max_limit_anim.value); - indicator_pos <- indicator_pos.map(|(value, min, max)| (value - min) / (max - min)); - indicator_pos <- all3(&indicator_pos, &input.set_thumb_size, &input.orientation); - eval indicator_pos((v) model.set_indicator_position(v)); + indicator_pos <- all_with4( + &model.start_value_animation.value, + &model.end_value_animation.value, + &min_limit_anim.value, + &max_limit_anim.value, + |start_value, end_value, min, max| { + let total = max - min; + ((start_value - min) / total, (end_value - min) / total) + }); + _eval <- all_with(&indicator_pos, &input.orientation, + f!((a, c) model.set_indicator_position(a.0, a.1, *c))); value_text_left_pos_x <- all3( &model.value_text_left.width, &model.value_text_dot.width, - &output.precision, + &output.resolution, ); value_text_left_pos_x <- value_text_left_pos_x.map( - // Center text if precision higher than 1.0 (integer display), else align to dot. + // Center text if resolution higher than 1.0 (integer display), else align to dot. |(left, dot, prec)| if *prec >= 1.0 {- *left / 2.0} else {- *left - *dot / 2.0} ); eval value_text_left_pos_x((x) model.value_text_left.set_x(*x)); @@ -725,8 +767,8 @@ impl Slider { frp::extend! { network start_editing <- input.start_value_editing.gate_not(&output.disabled); start_editing <- start_editing.gate(&input.show_value); - value_on_edit <- output.value.sample(&start_editing); - prec_on_edit <- output.precision.sample(&start_editing); + value_on_edit <- output.end_value.sample(&start_editing); + prec_on_edit <- output.resolution.sample(&start_editing); max_places_on_edit <- input.set_max_disp_decimal_places.sample(&start_editing); value_text_on_edit <- all3(&value_on_edit, &prec_on_edit, &max_places_on_edit); @@ -742,7 +784,7 @@ impl Slider { edit_success <- value_after_edit.map(|v| v.is_some()); value_after_edit <- value_after_edit.map(|v| v.unwrap_or_default()); prec_after_edit <- value_text_after_edit.map(|s| get_value_text_precision(s)); - prec_after_edit <- all2(&prec_after_edit, &input.set_default_precision); + prec_after_edit <- all2(&prec_after_edit, &input.set_default_resolution); prec_after_edit <- prec_after_edit.map(|(prec, default_prec)| prec.min(*default_prec)); value_after_edit <- all5( &value_after_edit, @@ -753,19 +795,19 @@ impl Slider { ).map(value_limit_clamp); output.editing <+ editing; - output.precision <+ prec_after_edit.gate(&edit_success); + output.resolution <+ prec_after_edit.gate(&edit_success); value_after_edit <- value_after_edit.gate(&edit_success); - output.value <+ value_after_edit; - model.value_animation.target <+ value_after_edit; + output.end_value <+ value_after_edit; + model.end_value_animation.target <+ value_after_edit; editing_event <- any2(&start_editing, &stop_editing); - editing <- all2(&editing, &output.precision).sample(&editing_event); + editing <- all2(&editing, &output.resolution).sample(&editing_event); eval editing((t) model.set_edit_mode(t)); }; } /// Initialize the compinent with default values. fn init_slider_defaults(&self) { - self.frp.set_default_precision(PRECISION_DEFAULT); + self.frp.set_default_resolution(RESOLUTION_DEFAULT); self.frp.set_precision_adjustment_margin(PRECISION_ADJUSTMENT_MARGIN); self.frp.set_precision_adjustment_step_size(PRECISION_ADJUSTMENT_STEP_SIZE); self.frp.set_max_precision_adjustment_steps(MAX_PRECISION_ADJUSTMENT_STEPS); @@ -776,6 +818,9 @@ impl Slider { self.frp.set_thumb_size(THUMB_SIZE_DEFAULT); self.show_value(true); self.orientation(Axis2::X); + self.enable_start_track_drag(true); + self.enable_end_track_drag(true); + self.enable_middle_track_drag(true); } } @@ -832,10 +877,10 @@ impl application::View for Slider { // === Value text formatting === // ============================= -/// Rounds and truncates a floating point value to a specified precision. -fn value_text_truncate((value, precision, max_digits): &(f32, f32, usize)) -> String { - if *precision < 1.0 || *max_digits == 0 { - let digits = (-precision.log10()).ceil() as usize; +/// Rounds and truncates a floating point value to a specified resolution. +fn value_text_truncate((value, resolution, max_digits): &(f32, f32, usize)) -> String { + if *resolution < 1.0 || *max_digits == 0 { + let digits = (-resolution.log10()).ceil() as usize; let digits = digits.min(*max_digits); format!("{value:.digits$}") } else { @@ -843,17 +888,21 @@ fn value_text_truncate((value, precision, max_digits): &(f32, f32, usize)) -> St } } -/// Rounds a floating point value to a specified precision and provides two strings: one with the +/// Rounds a floating point value to a specified resolution and provides two strings: one with the /// digits left of the decimal point, and one optional with the digits right of the decimal point. -fn display_value(value: &f32, precision: &f32, max_digits: &usize) -> (ImString, Option) { - let text = value_text_truncate(&(*value, *precision, *max_digits)); +fn display_value( + value: &f32, + resolution: &f32, + max_digits: &usize, +) -> (ImString, Option) { + let text = value_text_truncate(&(*value, *resolution, *max_digits)); let mut text_iter = text.split('.'); let text_left = text_iter.next().map(|s| s.to_im_string()).unwrap_or_default(); let text_right = text_iter.next().map(|s| s.to_im_string()); (text_left, text_right) } -/// Get the precision of a string containing a decimal value. +/// Get the resolution of a string containing a decimal value. fn get_value_text_precision(text: &str) -> f32 { let mut text_iter = text.split('.').skip(1); let text_right_len = text_iter.next().map(|t| t.len()); diff --git a/lib/rust/ensogl/component/slider/src/model.rs b/lib/rust/ensogl/component/slider/src/model.rs index 82a217e8dcee..f02dd82b7326 100644 --- a/lib/rust/ensogl/component/slider/src/model.rs +++ b/lib/rust/ensogl/component/slider/src/model.rs @@ -3,7 +3,6 @@ use ensogl_core::display::shape::*; use ensogl_core::prelude::*; -use crate::Kind; use crate::LabelPosition; use ensogl_core::application::Application; @@ -21,8 +20,6 @@ use ensogl_tooltip::Tooltip; // === Constants === // ================= -/// Size of the margin around the component's shapes for proper anti-aliasing. -const COMPONENT_MARGIN: f32 = 4.0; /// Default component width on initialization. const COMPONENT_WIDTH_DEFAULT: f32 = 200.0; /// Default component height on initialization. @@ -47,8 +44,6 @@ impl Background { fn new() -> Self { let width: Var = "input_size.x".into(); let height: Var = "input_size.y".into(); - let width = width - COMPONENT_MARGIN.px() * 2.0; - let height = height - COMPONENT_MARGIN.px() * 2.0; let shape = Rect((&width, &height)).corners_radius(&height / 2.0); let shape = shape.into(); Background { width, height, shape } @@ -72,62 +67,33 @@ mod background { /// Track shape that fills the slider proportional to the slider value. mod track { use super::*; - ensogl_core::shape! { above = [background]; pointer_events = false; alignment = center; - (style:Style, slider_fraction_horizontal:f32, slider_fraction_vertical:f32, color:Vector4) { + (style:Style, start: f32, end:f32, color:Vector4) { let Background{width,height,shape: background} = Background::new(); - let track = Rect(( - &width * &slider_fraction_horizontal, - &height * &slider_fraction_vertical, - )); - let track = track.translate_x(&width * (&slider_fraction_horizontal - 1.0) * 0.5); - let track = track.translate_y(&height * (&slider_fraction_vertical - 1.0) * 0.5); + let length = &end - &start; + let track = Rect((&width * &length, &height)); + let track = track.translate_x(&width * (length - 1.0) * 0.5 + &width * start); let track = track.intersection(background).fill(color); track.into() } } } -/// Thumb shape that moves along the slider proportional to the slider value. -mod thumb { - use super::*; - - ensogl_core::shape! { - above = [background]; - pointer_events = false; - alignment = center; - (style:Style, slider_fraction:f32, thumb_width:f32, thumb_height:f32, color:Vector4) { - let Background{width,height,shape: background} = Background::new(); - let thumb_width = &width * &thumb_width; - let thumb_height = &height * &thumb_height; - let thumb = Rect((&thumb_width, &thumb_height)); - let thumb = thumb.corners_radius(&thumb_height / 2.0); - let range_x = &width - &thumb_width; - let range_y = &height - &thumb_height; - let thumb = thumb.translate_x(-&range_x * 0.5 + &range_x * &slider_fraction); - let thumb = thumb.translate_y(-&range_y * 0.5 + &range_y * &slider_fraction); - let thumb = thumb.intersection(background).fill(color); - thumb.into() - } - } -} /// Triangle shape used as an overflow indicator on either side of the range. mod overflow { use super::*; ensogl_core::shape! { - above = [background, track, thumb]; + above = [background, track]; pointer_events = false; alignment = center; (style:Style, color:Vector4) { let width: Var = "input_size.x".into(); let height: Var = "input_size.y".into(); - let width = width - COMPONENT_MARGIN.px() * 2.0; - let height = height - COMPONENT_MARGIN.px() * 2.0; let color = style.get_color(theme::overflow::color); let triangle = Triangle(width, height); @@ -148,70 +114,66 @@ mod overflow { #[derive(Debug)] pub struct Model { /// Background element - pub background: background::View, + pub background: background::View, /// Slider track element that fills the slider proportional to the slider value. - pub track: track::View, - /// Slider thumb element that moves across the slider proportional to the slider value. - pub thumb: thumb::View, + pub track: track::View, /// Indicator for overflow when the value is below the lower limit. - pub overflow_lower: overflow::View, + pub overflow_lower: overflow::View, /// Indicator for overflow when the value is above the upper limit. - pub overflow_upper: overflow::View, + pub overflow_upper: overflow::View, /// Slider label that is shown next to the slider. - pub label: text::Text, + pub label: text::Text, /// Textual representation of the slider value, only part left of the decimal point. - pub value_text_left: text::Text, + pub value_text_left: text::Text, /// Decimal point that is used to display non-integer slider values. - pub value_text_dot: text::Text, + pub value_text_dot: text::Text, /// Textual representation of the slider value, only part right of the decimal point. - pub value_text_right: text::Text, + pub value_text_right: text::Text, /// Textual representation of the slider value used when editing the value as text input. - pub value_text_edit: text::Text, + pub value_text_edit: text::Text, /// Tooltip component showing either a tooltip message or slider precision changes. - pub tooltip: Tooltip, - /// Animation component that smoothly adjusts the slider value on large jumps. - pub value_animation: Animation, + pub tooltip: Tooltip, + /// Animation component that smoothly adjusts the slider start value on large jumps. + pub start_value_animation: Animation, + /// Animation component that smoothly adjusts the slider end value on large jumps. + pub end_value_animation: Animation, /// Root of the display object. - pub root: display::object::Instance, + pub root: display::object::Instance, + /// The display object containing the text value of the slider. + pub value: display::object::Instance, } impl Model { /// Create a new slider model. pub fn new(app: &Application, frp_network: &frp::Network) -> Self { let root = display::object::Instance::new(); + let value = display::object::Instance::new(); let label = app.new_view::(); let value_text_left = app.new_view::(); let value_text_dot = app.new_view::(); let value_text_right = app.new_view::(); let value_text_edit = app.new_view::(); let tooltip = Tooltip::new(app); - let value_animation = Animation::new_non_init(frp_network); + let start_value_animation = Animation::new_non_init(frp_network); + let end_value_animation = Animation::new_non_init(frp_network); let background = background::View::new(); let track = track::View::new(); - let thumb = thumb::View::new(); let overflow_lower = overflow::View::new(); let overflow_upper = overflow::View::new(); - let scene = &app.display.default_scene; let style = StyleWatch::new(&app.display.default_scene.style_sheet); root.add_child(&background); root.add_child(&track); root.add_child(&label); - root.add_child(&value_text_left); - root.add_child(&value_text_dot); - root.add_child(&value_text_right); + root.add_child(&value); + value.add_child(&value_text_left); + value.add_child(&value_text_dot); + value.add_child(&value_text_right); app.display.default_scene.add_child(&tooltip); - value_text_left.add_to_scene_layer(&scene.layers.label); - value_text_dot.add_to_scene_layer(&scene.layers.label); - value_text_right.add_to_scene_layer(&scene.layers.label); - value_text_edit.add_to_scene_layer(&scene.layers.label); - label.add_to_scene_layer(&scene.layers.label); - let model = Self { background, track, - thumb, overflow_lower, overflow_upper, label, @@ -220,8 +182,10 @@ impl Model { value_text_right, value_text_edit, tooltip, - value_animation, + start_value_animation, + end_value_animation, root, + value, }; model.init(style) } @@ -237,7 +201,6 @@ impl Model { self.label.set_font(text::font::DEFAULT_FONT); self.background.color.set(background_color.into()); self.track.color.set(track_color.into()); - self.thumb.color.set(track_color.into()); self.update_size(Vector2(COMPONENT_WIDTH_DEFAULT, COMPONENT_HEIGHT_DEFAULT)); self.value_text_dot.set_content("."); self @@ -245,16 +208,16 @@ impl Model { /// Set the component size. pub fn update_size(&self, size: Vector2) { - let margin = Vector2(COMPONENT_MARGIN * 2.0, COMPONENT_MARGIN * 2.0); - self.background.set_size(size + margin); - self.track.set_size(size + margin); - self.thumb.set_size(size + margin); + self.background.set_size(size); + self.track.set_size(size); + self.background.set_x(size.x / 2.0); + self.track.set_x(size.x / 2.0); + self.value.set_x(size.x / 2.0); } /// Set the color of the slider track or thumb. pub fn set_indicator_color(&self, color: &color::Lcha) { self.track.color.set(color::Rgba::from(color).into()); - self.thumb.color.set(color::Rgba::from(color).into()); } /// Set the color of the slider background. @@ -262,43 +225,22 @@ impl Model { self.background.color.set(color::Rgba::from(color).into()); } - /// Set whether the lower overfow marker is visible. - pub fn kind(&self, indicator: &Kind) { - match indicator { - Kind::SingleValue => { - self.root.add_child(&self.track); - self.root.remove_child(&self.thumb); - } - Kind::Scrollbar(_) => { - self.root.add_child(&self.thumb); - self.root.remove_child(&self.track); - } - } - } - /// Set the position of the value indicator. - pub fn set_indicator_position(&self, (fraction, size, orientation): &(f32, f32, Axis2)) { - self.thumb.slider_fraction.set(*fraction); + pub fn set_indicator_position(&self, start: f32, fraction: f32, orientation: Axis2) { match orientation { Axis2::X => { - self.track.slider_fraction_horizontal.set(fraction.clamp(0.0, 1.0)); - self.track.slider_fraction_vertical.set(1.0); - self.thumb.thumb_width.set(*size); - self.thumb.thumb_height.set(1.0); + self.track.start.set(start.clamp(0.0, 1.0)); + self.track.end.set(fraction.clamp(0.0, 1.0)); } Axis2::Y => { - self.track.slider_fraction_horizontal.set(1.0); - self.track.slider_fraction_vertical.set(fraction.clamp(0.0, 1.0)); - self.thumb.thumb_width.set(1.0); - self.thumb.thumb_height.set(*size); + self.track.end.set(1.0); } } } /// Set the size and orientation of the overflow markers. pub fn set_overflow_marker_shape(&self, (size, orientation): &(f32, Axis2)) { - let margin = Vector2(COMPONENT_MARGIN * 2.0, COMPONENT_MARGIN * 2.0); - let size = Vector2(*size, *size) * OVERFLOW_MARKER_SIZE + margin; + let size = Vector2(*size, *size) * OVERFLOW_MARKER_SIZE; self.overflow_lower.set_size(size); self.overflow_upper.set_size(size); match orientation { @@ -386,13 +328,9 @@ impl Model { /// Set whether the slider value text is hidden. pub fn show_value(&self, visible: bool) { if visible { - self.root.add_child(&self.value_text_left); - self.root.add_child(&self.value_text_dot); - self.root.add_child(&self.value_text_right); + self.root.add_child(&self.value); } else { - self.root.remove_child(&self.value_text_left); - self.root.remove_child(&self.value_text_dot); - self.root.remove_child(&self.value_text_right); + self.root.remove_child(&self.value); } } @@ -407,21 +345,19 @@ impl Model { /// Set whether the value is being edited. This hides the value display and shows a text editor /// field to enter a new value. - pub fn set_edit_mode(&self, (editing, precision): &(bool, f32)) { + pub fn set_edit_mode(&self, (editing, _precision): &(bool, f32)) { if *editing { - self.root.remove_child(&self.value_text_left); - self.root.remove_child(&self.value_text_dot); - self.root.remove_child(&self.value_text_right); + self.root.remove_child(&self.value); self.root.add_child(&self.value_text_edit); self.value_text_edit.deprecated_focus(); self.value_text_edit.add_cursor_at_front(); self.value_text_edit.cursor_select_to_text_end(); } else { - self.root.add_child(&self.value_text_left); - if *precision < 1.0 { - self.root.add_child(&self.value_text_dot); - self.root.add_child(&self.value_text_right); - } + self.root.add_child(&self.value); + // if *precision < 1.0 { + // self.root.add_child(&self.value_text_dot); + // self.root.add_child(&self.value_text_right); + // } self.root.remove_child(&self.value_text_edit); self.value_text_edit.deprecated_defocus(); self.value_text_edit.remove_all_cursors(); @@ -431,11 +367,11 @@ impl Model { /// Set whether the value display decimal point and the text right of it are visible. pub fn set_value_text_right_visible(&self, enabled: bool) { if enabled { - self.root.add_child(&self.value_text_dot); - self.root.add_child(&self.value_text_right); + self.value.add_child(&self.value_text_dot); + self.value.add_child(&self.value_text_right); } else { - self.root.remove_child(&self.value_text_dot); - self.root.remove_child(&self.value_text_right); + self.value.remove_child(&self.value_text_dot); + self.value.remove_child(&self.value_text_right); } } diff --git a/lib/rust/ensogl/core/src/display/object/event.rs b/lib/rust/ensogl/core/src/display/object/event.rs index e989ec486a60..a6894a3dbe99 100644 --- a/lib/rust/ensogl/core/src/display/object/event.rs +++ b/lib/rust/ensogl/core/src/display/object/event.rs @@ -37,12 +37,13 @@ pub enum State { #[allow(missing_docs)] #[derive(Clone, CloneRef, Debug)] pub struct SomeEvent { - pub data: frp::AnyData, - state: Rc>, + pub data: frp::AnyData, + state: Rc>, + current_target: Rc>>, /// Indicates whether the event participates in the capturing phase. - pub captures: Rc>, + pub captures: Rc>, /// Indicates whether the event participates in the bubbling phase. - pub bubbles: Rc>, + pub bubbles: Rc>, } impl SomeEvent { @@ -50,9 +51,10 @@ impl SomeEvent { pub fn new(target: Option, payload: T) -> Self { let event = Event::new(target, payload); let state = event.state.clone_ref(); + let current_target = event.current_target.clone_ref(); let captures = Rc::new(Cell::new(true)); let bubbles = Rc::new(Cell::new(true)); - Self { data: frp::AnyData::new(event), state, captures, bubbles } + Self { data: frp::AnyData::new(event), state, current_target, captures, bubbles } } /// The [`State]` of the event. @@ -69,6 +71,12 @@ impl SomeEvent { pub fn set_bubbling(&self, value: bool) { self.bubbles.set(value); } + + /// Set the current target of the event. This is internal function and should not be used + /// directly. + pub(crate) fn set_current_target(&self, target: Option<&Instance>) { + self.current_target.replace(target.map(|t| t.downgrade())); + } } impl Default for SomeEvent { @@ -113,9 +121,10 @@ impl Debug for Event { #[derivative(Default(bound = "T: Default"))] pub struct EventData { #[deref] - pub payload: T, - target: Option, - state: Rc>, + pub payload: T, + target: Option, + current_target: Rc>>, + state: Rc>, } impl Debug for EventData { @@ -130,7 +139,8 @@ impl Debug for EventData { impl Event { fn new(target: Option, payload: T) -> Self { let state = default(); - let data = Rc::new(EventData { payload, target, state }); + let current_target = Rc::new(RefCell::new(target.clone())); + let data = Rc::new(EventData { payload, target, current_target, state }); Self { data } } @@ -152,6 +162,18 @@ impl Event { pub fn target(&self) -> Option { self.data.target.as_ref().and_then(|t| t.upgrade()) } + + /// The current target for the event, as the event traverses the display object hierarchy. It + /// always refers to the element to which the event handler has been attached, as opposed to + /// [`Self::target`], which identifies the element on which the event occurred and which may be + /// its descendant. + /// + /// # Important Note + /// The value of [`Self::current_target`] is only available while the event is being handled. If + /// store the event in a variable and read this property later, the value will be [`None`]. + pub fn current_target(&self) -> Option { + self.data.current_target.borrow().as_ref().and_then(|t| t.upgrade()) + } } diff --git a/lib/rust/ensogl/core/src/display/object/instance.rs b/lib/rust/ensogl/core/src/display/object/instance.rs index 7d9933b095be..7cdb66f00bb7 100644 --- a/lib/rust/ensogl/core/src/display/object/instance.rs +++ b/lib/rust/ensogl/core/src/display/object/instance.rs @@ -2217,7 +2217,7 @@ impl InstanceDef { /// Get reversed parent chain of this display object (`[root, child_of root, ..., parent, /// self]`). The last item is this object. - fn rev_parent_chain(&self) -> Vec { + pub fn rev_parent_chain(&self) -> Vec { let mut vec = default(); Self::build_rev_parent_chain(&mut vec, Some(self.clone_ref().into())); vec @@ -2415,6 +2415,7 @@ impl InstanceDef { if event.captures.get() { for object in &rev_parent_chain { if !event.is_cancelled() { + event.set_current_target(Some(object)); object.event.capturing_fan.emit(&event.data); } else { break; @@ -2430,12 +2431,14 @@ impl InstanceDef { if event.bubbles.get() { for object in rev_parent_chain.iter().rev() { if !event.is_cancelled() { + event.set_current_target(Some(object)); object.event.bubbling_fan.emit(&event.data); } else { break; } } } + event.set_current_target(None); } fn new_event(&self, payload: T) -> event::SomeEvent diff --git a/lib/rust/ensogl/core/src/display/scene.rs b/lib/rust/ensogl/core/src/display/scene.rs index 9f0888fabe07..6af004a1a104 100644 --- a/lib/rust/ensogl/core/src/display/scene.rs +++ b/lib/rust/ensogl/core/src/display/scene.rs @@ -1024,15 +1024,21 @@ impl SceneData { let layer = object.display_layer(); let camera = layer.map_or(self.camera(), |l| l.camera()); let origin_clip_space = camera.view_projection_matrix() * origin_world_space; - let inv_object_matrix = object.transformation_matrix().try_inverse().unwrap(); - - let shape = camera.screen(); - let clip_space_z = origin_clip_space.z; - let clip_space_x = origin_clip_space.w * 2.0 * screen_pos.x / shape.width; - let clip_space_y = origin_clip_space.w * 2.0 * screen_pos.y / shape.height; - let clip_space = Vector4(clip_space_x, clip_space_y, clip_space_z, origin_clip_space.w); - let world_space = camera.inversed_view_projection_matrix() * clip_space; - (inv_object_matrix * world_space).xy() + if let Some(inv_object_matrix) = object.transformation_matrix().try_inverse() { + let shape = camera.screen(); + let clip_space_z = origin_clip_space.z; + let clip_space_x = origin_clip_space.w * 2.0 * screen_pos.x / shape.width; + let clip_space_y = origin_clip_space.w * 2.0 * screen_pos.y / shape.height; + let clip_space = Vector4(clip_space_x, clip_space_y, clip_space_z, origin_clip_space.w); + let world_space = camera.inversed_view_projection_matrix() * clip_space; + (inv_object_matrix * world_space).xy() + } else { + warn!( + "The object transformation matrix is not invertible, \ + this can cause visual artifacts." + ); + default() + } } } diff --git a/lib/rust/ensogl/examples/Cargo.toml b/lib/rust/ensogl/examples/Cargo.toml index bee6d046b342..04a5773fb1eb 100644 --- a/lib/rust/ensogl/examples/Cargo.toml +++ b/lib/rust/ensogl/examples/Cargo.toml @@ -30,4 +30,4 @@ ensogl-example-slider = { path = "slider" } ensogl-example-sprite-system = { path = "sprite-system" } ensogl-example-sprite-system-benchmark = { path = "sprite-system-benchmark" } ensogl-example-text-area = { path = "text-area" } -ensogl-example-vector-editor = { path = "vector-editor" } +ensogl-example-list-editor = { path = "list-editor" } diff --git a/lib/rust/ensogl/examples/vector-editor/Cargo.toml b/lib/rust/ensogl/examples/list-editor/Cargo.toml similarity index 58% rename from lib/rust/ensogl/examples/vector-editor/Cargo.toml rename to lib/rust/ensogl/examples/list-editor/Cargo.toml index de556f9f058b..8add04d10ad2 100644 --- a/lib/rust/ensogl/examples/vector-editor/Cargo.toml +++ b/lib/rust/ensogl/examples/list-editor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ensogl-example-vector-editor" +name = "ensogl-example-list-editor" version = "0.1.0" authors = ["Enso Team "] edition = "2021" @@ -8,9 +8,11 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] +enso-frp = { path = "../../../frp" } ensogl-core = { path = "../../core" } -wasm-bindgen = { workspace = true } -ensogl-hardcoded-theme = { path = "../../../ensogl/app/theme/hardcoded" } +ensogl-list-editor = { path = "../../component/list-editor" } +ensogl-slider = { path = "../../component/slider" } +ensogl-text-msdf = { path = "../../component/text/src/font/msdf" } # Stop wasm-pack from running wasm-opt, because we run it from our build scripts in order to customize options. [package.metadata.wasm-pack.profile.release] diff --git a/lib/rust/ensogl/examples/list-editor/src/lib.rs b/lib/rust/ensogl/examples/list-editor/src/lib.rs new file mode 100644 index 000000000000..0ea8ec676e96 --- /dev/null +++ b/lib/rust/ensogl/examples/list-editor/src/lib.rs @@ -0,0 +1,93 @@ +//! An example scene showing the list editor component usage. + +// === Features === +#![feature(associated_type_defaults)] +#![feature(drain_filter)] +#![feature(fn_traits)] +#![feature(trait_alias)] +#![feature(type_alias_impl_trait)] +#![feature(unboxed_closures)] +// === Standard Linter Configuration === +#![deny(non_ascii_idents)] +#![warn(unsafe_code)] +#![allow(clippy::bool_to_int_with_if)] +#![allow(clippy::let_and_return)] +// === Non-Standard Linter Configuration === +#![warn(missing_copy_implementations)] +#![warn(missing_debug_implementations)] +#![warn(missing_docs)] +#![warn(trivial_casts)] +#![warn(trivial_numeric_casts)] +#![warn(unused_import_braces)] +#![warn(unused_qualifications)] + +use ensogl_core::prelude::*; + +use enso_frp as frp; +use ensogl_core::application::Application; +use ensogl_core::display; +use ensogl_core::display::navigation::navigator::Navigator; +use ensogl_list_editor::ListEditor; +use ensogl_slider as slider; +use ensogl_text_msdf::run_once_initialized; +use std::mem; + + + +// =================== +// === Entry Point === +// =================== + +// A global FRP network used to handle events from the list editor. +ensogl_core::define_endpoints_2! {} + +/// The example entry point. +#[entry_point] +#[allow(dead_code)] +pub fn main() { + run_once_initialized(run); +} + +fn run() { + let app = Application::new("root"); + let world = app.display.clone(); + let scene = &world.default_scene; + let camera = scene.camera().clone_ref(); + let navigator = Navigator::new(scene, &camera); + let vector_editor = ListEditor::new(&app.cursor); + + let slider1 = app.new_view::(); + slider1.set_size((200.0, 24.0)); + + let slider2 = app.new_view::(); + slider2.set_size((200.0, 24.0)); + + let slider3 = app.new_view::(); + slider3.set_size((200.0, 24.0)); + + let frp = Frp::new(); + let network = frp.network(); + + frp::extend! { network + vector_editor.insert <+ vector_editor.request_new_item.map(move |index| { + let slider = app.new_view::(); + slider.set_size((200.0, 24.0)); + (**index, Rc::new(RefCell::new(Some(slider)))) + }); + } + + vector_editor.push(slider1); + vector_editor.push(slider2); + vector_editor.push(slider3); + + let root = display::object::Instance::new(); + root.set_size(Vector2(300.0, 100.0)); + root.add_child(&vector_editor); + world.add_child(&root); + + world.keep_alive_forever(); + mem::forget(frp); + mem::forget(navigator); + mem::forget(root); + mem::forget(vector_editor); +} diff --git a/lib/rust/ensogl/examples/slider/src/lib.rs b/lib/rust/ensogl/examples/slider/src/lib.rs index 8b01e8c83a85..cf01de185cfb 100644 --- a/lib/rust/ensogl/examples/slider/src/lib.rs +++ b/lib/rust/ensogl/examples/slider/src/lib.rs @@ -1,4 +1,4 @@ -//! A debug scene which shows the slider component +//! An example scene showing the slider component usage. // === Features === #![feature(associated_type_defaults)] @@ -35,22 +35,6 @@ use ensogl_text_msdf::run_once_initialized; -// =================================== -// === Basic slider initialization === -// =================================== - -/// Create a basic slider. -fn make_slider(app: &Application) -> slider::Slider { - let slider = app.new_view::(); - // slider.frp.set_background_color(color::Lcha(0.8, 0.0, 0.0, 1.0)); - // slider.frp.set_max_value(5.0); - // slider.frp.set_default_value(1.0); - // slider.frp.set_value(1.0); - slider -} - - - // ======================== // === Model definition === // ======================== @@ -81,7 +65,7 @@ impl Model { /// Add example sliders to scene. fn init_sliders(&self) { - let slider1 = make_slider(&self.app); + let slider1 = self.app.new_view::(); slider1.set_size((200.0, 24.0)); slider1.set_y(-120.0); slider1.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); @@ -92,106 +76,111 @@ impl Model { self.root.add_child(&slider1); self.sliders.borrow_mut().push(slider1); - let slider2 = make_slider(&self.app); - slider2.set_size((400.0, 50.0)); - slider2.set_y(-60.0); - slider2.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider2.frp.set_slider_disabled(true); - slider2.frp.set_label("Disabled"); - self.root.add_child(&slider2); - self.sliders.borrow_mut().push(slider2); - - let slider3 = make_slider(&self.app); - slider3.set_size((400.0, 50.0)); - slider3.set_y(0.0); - slider3.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider3.frp.set_default_value(100.0); - slider3.frp.set_value(100.0); - slider3.frp.set_max_value(500.0); - slider3.frp.set_label("Adaptive lower limit"); - slider3.frp.set_lower_limit_type(slider::SliderLimit::Adaptive); - self.root.add_child(&slider3); - self.sliders.borrow_mut().push(slider3); - - let slider4 = make_slider(&self.app); - slider4.set_size((400.0, 50.0)); - slider4.set_y(60.0); - slider4.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider4.frp.set_label("Adaptive upper limit"); - slider4.frp.set_label_position(slider::LabelPosition::Inside); - slider4.frp.set_upper_limit_type(slider::SliderLimit::Adaptive); - self.root.add_child(&slider4); - self.sliders.borrow_mut().push(slider4); - - let slider5 = make_slider(&self.app); - slider5.set_size((75.0, 230.0)); - slider5.set_y(-35.0); - slider5.set_x(275.0); - slider5.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider5.frp.set_label("Hard limits"); - slider5.frp.orientation(Axis2::Y); - slider5.frp.set_max_disp_decimal_places(4); - self.root.add_child(&slider5); - self.sliders.borrow_mut().push(slider5); - - let slider6 = make_slider(&self.app); - slider6.set_size((75.0, 230.0)); - slider6.set_y(-35.0); - slider6.set_x(375.0); - slider6.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider6.frp.set_label("Soft\nlimits"); - slider6.frp.set_label_position(slider::LabelPosition::Inside); - slider6.frp.set_lower_limit_type(slider::SliderLimit::Soft); - slider6.frp.set_upper_limit_type(slider::SliderLimit::Soft); - slider6.frp.orientation(Axis2::Y); - slider6.frp.set_max_disp_decimal_places(4); - self.root.add_child(&slider6); - self.sliders.borrow_mut().push(slider6); - - let slider7 = make_slider(&self.app); - slider7.set_size((400.0, 10.0)); - slider7.set_y(-160.0); - slider7.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider7.frp.show_value(false); - slider7.frp.set_precision_adjustment_disabled(true); - slider7.frp.kind(slider::Kind::Scrollbar(0.1)); - slider7.frp.set_thumb_size(0.1); - self.root.add_child(&slider7); - self.sliders.borrow_mut().push(slider7); - - let slider8 = make_slider(&self.app); - slider8.set_size((400.0, 10.0)); - slider8.set_y(-180.0); - slider8.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider8.frp.show_value(false); - slider8.frp.set_precision_adjustment_disabled(true); - slider8.frp.kind(slider::Kind::Scrollbar(0.25)); - slider8.frp.set_thumb_size(0.25); - self.root.add_child(&slider8); - self.sliders.borrow_mut().push(slider8); - - let slider9 = make_slider(&self.app); - slider9.set_size((400.0, 10.0)); - slider9.set_y(-200.0); - slider9.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider9.frp.show_value(false); - slider9.frp.set_precision_adjustment_disabled(true); - slider9.frp.kind(slider::Kind::Scrollbar(0.5)); - slider9.frp.set_thumb_size(0.5); - self.root.add_child(&slider9); - self.sliders.borrow_mut().push(slider9); - - let slider10 = make_slider(&self.app); - slider10.set_size((10.0, 230)); - slider10.set_y(-35.0); - slider10.set_x(430.0); - slider10.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider10.frp.show_value(false); - slider10.frp.set_precision_adjustment_disabled(true); - slider10.frp.kind(slider::Kind::Scrollbar(0.1)); - slider10.frp.orientation(Axis2::Y); - self.root.add_child(&slider10); - self.sliders.borrow_mut().push(slider10); + // # IMPORTANT + // This code is commented because the slider implementation is not finished yet. Please + // refer to the doc comments in the slider's module to learn more. + + // + // let slider2 = self.app.new_view::(); + // slider2.set_size((400.0, 50.0)); + // slider2.set_y(-60.0); + // slider2.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider2.frp.set_slider_disabled(true); + // slider2.frp.set_label("Disabled"); + // self.root.add_child(&slider2); + // self.sliders.borrow_mut().push(slider2); + // + // let slider3 = self.app.new_view::(); + // slider3.set_size((400.0, 50.0)); + // slider3.set_y(0.0); + // slider3.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider3.frp.set_default_value(100.0); + // slider3.frp.set_value(100.0); + // slider3.frp.set_max_value(500.0); + // slider3.frp.set_label("Adaptive lower limit"); + // slider3.frp.set_lower_limit_type(slider::SliderLimit::Adaptive); + // self.root.add_child(&slider3); + // self.sliders.borrow_mut().push(slider3); + // + // let slider4 = self.app.new_view::(); + // slider4.set_size((400.0, 50.0)); + // slider4.set_y(60.0); + // slider4.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider4.frp.set_label("Adaptive upper limit"); + // slider4.frp.set_label_position(slider::LabelPosition::Inside); + // slider4.frp.set_upper_limit_type(slider::SliderLimit::Adaptive); + // self.root.add_child(&slider4); + // self.sliders.borrow_mut().push(slider4); + // + // let slider5 = self.app.new_view::(); + // slider5.set_size((75.0, 230.0)); + // slider5.set_y(-35.0); + // slider5.set_x(275.0); + // slider5.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider5.frp.set_label("Hard limits"); + // slider5.frp.orientation(Axis2::Y); + // slider5.frp.set_max_disp_decimal_places(4); + // self.root.add_child(&slider5); + // self.sliders.borrow_mut().push(slider5); + // + // let slider6 = self.app.new_view::(); + // slider6.set_size((75.0, 230.0)); + // slider6.set_y(-35.0); + // slider6.set_x(375.0); + // slider6.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider6.frp.set_label("Soft\nlimits"); + // slider6.frp.set_label_position(slider::LabelPosition::Inside); + // slider6.frp.set_lower_limit_type(slider::SliderLimit::Soft); + // slider6.frp.set_upper_limit_type(slider::SliderLimit::Soft); + // slider6.frp.orientation(Axis2::Y); + // slider6.frp.set_max_disp_decimal_places(4); + // self.root.add_child(&slider6); + // self.sliders.borrow_mut().push(slider6); + // + // let slider7 = self.app.new_view::(); + // slider7.set_size((400.0, 10.0)); + // slider7.set_y(-160.0); + // slider7.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider7.frp.show_value(false); + // slider7.frp.set_precision_adjustment_disabled(true); + // slider7.frp.kind(slider::Kind::Scrollbar(0.1)); + // slider7.frp.set_thumb_size(0.1); + // self.root.add_child(&slider7); + // self.sliders.borrow_mut().push(slider7); + // + // let slider8 = self.app.new_view::(); + // slider8.set_size((400.0, 10.0)); + // slider8.set_y(-180.0); + // slider8.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider8.frp.show_value(false); + // slider8.frp.set_precision_adjustment_disabled(true); + // slider8.frp.kind(slider::Kind::Scrollbar(0.25)); + // slider8.frp.set_thumb_size(0.25); + // self.root.add_child(&slider8); + // self.sliders.borrow_mut().push(slider8); + // + // let slider9 = self.app.new_view::(); + // slider9.set_size((400.0, 10.0)); + // slider9.set_y(-200.0); + // slider9.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider9.frp.show_value(false); + // slider9.frp.set_precision_adjustment_disabled(true); + // slider9.frp.kind(slider::Kind::Scrollbar(0.5)); + // slider9.frp.set_thumb_size(0.5); + // self.root.add_child(&slider9); + // self.sliders.borrow_mut().push(slider9); + // + // let slider10 = self.app.new_view::(); + // slider10.set_size((10.0, 230)); + // slider10.set_y(-35.0); + // slider10.set_x(430.0); + // slider10.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); + // slider10.frp.show_value(false); + // slider10.frp.set_precision_adjustment_disabled(true); + // slider10.frp.kind(slider::Kind::Scrollbar(0.1)); + // slider10.frp.orientation(Axis2::Y); + // self.root.add_child(&slider10); + // self.sliders.borrow_mut().push(slider10); } /// Drop all sliders from scene. diff --git a/lib/rust/ensogl/examples/src/lib.rs b/lib/rust/ensogl/examples/src/lib.rs index 79e633cf0bf8..85f4ecd364bd 100644 --- a/lib/rust/ensogl/examples/src/lib.rs +++ b/lib/rust/ensogl/examples/src/lib.rs @@ -41,6 +41,7 @@ pub use ensogl_example_easing_animator as easing_animator; pub use ensogl_example_focus_management as focus_management; pub use ensogl_example_grid_view as grid_view; pub use ensogl_example_instance_ordering as instance_ordering; +pub use ensogl_example_list_editor as list_editor; pub use ensogl_example_list_view as list_view; pub use ensogl_example_mouse_events as mouse_events; pub use ensogl_example_profiling_run_graph as profiling_run_graph; diff --git a/lib/rust/ensogl/examples/vector-editor/src/lib.rs b/lib/rust/ensogl/examples/vector-editor/src/lib.rs deleted file mode 100644 index 1085cb2e97ab..000000000000 --- a/lib/rust/ensogl/examples/vector-editor/src/lib.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Example scene showing the usage of built-in vector editor component. -//! -//! TODO[WD]: This is work in progress and will be changed in the upcoming PRs. - -// === Standard Linter Configuration === -#![deny(non_ascii_idents)] -#![warn(unsafe_code)] -#![allow(clippy::bool_to_int_with_if)] -#![allow(clippy::let_and_return)] - -use ensogl_core::display::shape::compound::rectangle::*; -use ensogl_core::display::world::*; -use ensogl_core::prelude::*; - -use ensogl_core::control::io::mouse; -use ensogl_core::data::color; -use ensogl_core::display; -use ensogl_core::display::navigation::navigator::Navigator; -use ensogl_core::display::object::ObjectOps; - - - -// ============== -// === Events === -// ============== - -#[derive(Clone, CloneRef, Debug, Default)] -pub struct MouseOver; - - -// ============ -// === Glob === -// ============ - -pub mod glob { - use super::*; - ensogl_core::define_endpoints_2! { - Input { - } - Output { - } - } -} - -// =========== -// === FRP === -// =========== - -ensogl_core::define_endpoints_2! { - Input { - } - Output { - } -} - - -#[derive(Derivative, CloneRef, Debug, Deref)] -#[derivative(Clone(bound = ""))] -pub struct VectorEditor { - #[deref] - pub frp: Frp, - display_object: display::object::Instance, - model: Rc>>, -} - -#[derive(Debug, Derivative)] -#[derivative(Default(bound = ""))] -pub struct Model { - items: Vec, -} - -impl VectorEditor { - pub fn new() -> Self { - let frp = Frp::new(); - let display_object = display::object::Instance::new(); - let model = default(); - display_object.use_auto_layout().set_gap((10.0, 10.0)); - Self { frp, display_object, model }.init() - } - - fn init(self) -> Self { - let network = self.frp.network(); - let event_handler = self.display_object.on_event::(); - frp::extend! { network - eval_ event_handler ([] { - warn!("Mouse up in parent"); - }); - } - self - } -} - -impl VectorEditor { - fn append(&self, item: T) { - self.add_child(&item); - self.model.borrow_mut().items.push(item); - } -} - -impl display::Object for VectorEditor { - fn display_object(&self) -> &display::object::Instance { - &self.display_object - } -} - -impl Default for VectorEditor { - fn default() -> Self { - Self::new() - } -} - - -// =================== -// === 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 vector_editor = VectorEditor::::new(); - - - let shape1 = Circle().build(|t| { - t.set_size(Vector2::new(100.0, 100.0)) - .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)) - .keep_bottom_left_quarter(); - }); - let shape2 = RoundedRectangle(10.0).build(|t| { - t.set_size(Vector2::new(100.0, 100.0)) - .set_color(color::Rgba::new(0.5, 0.0, 0.0, 0.3)) - .set_inset_border(5.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 1.0, 1.0)); - }); - - - let glob_frp = glob::Frp::new(); - let glob_frp_network = glob_frp.network(); - - let shape1_over = shape1.on_event::(); - frp::extend! { glob_frp_network - eval_ shape1_over ([] { - warn!("Shape 1 over"); - }); - } - - vector_editor.append(shape1); - vector_editor.append(shape2); - - let root = display::object::Instance::new(); - root.set_size(Vector2::new(300.0, 100.0)); - root.add_child(&vector_editor); - world.add_child(&root); - - world.keep_alive_forever(); - mem::forget(glob_frp); - mem::forget(navigator); - mem::forget(root); - mem::forget(vector_editor); -} diff --git a/lib/rust/frp/src/nodes.rs b/lib/rust/frp/src/nodes.rs index 5b58ed69330e..e0b6b6e57d0c 100644 --- a/lib/rust/frp/src/nodes.rs +++ b/lib/rust/frp/src/nodes.rs @@ -1056,6 +1056,172 @@ impl Network { self.register(OwnedMap4::new(label, t1, t2, t3, t4, f)) } + /// Specialized version of `map`. + pub fn map5( + &self, + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + f: F, + ) -> Stream + where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T: Data, + F: 'static + Fn(&Output, &Output, &Output, &Output, &Output) -> T, + { + self.register(OwnedMap5::new(label, t1, t2, t3, t4, t5, f)) + } + + /// Specialized version of `map`. + pub fn map6( + &self, + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + f: F, + ) -> Stream + where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T: Data, + F: 'static + + Fn(&Output, &Output, &Output, &Output, &Output, &Output) -> T, + { + self.register(OwnedMap6::new(label, t1, t2, t3, t4, t5, t6, f)) + } + + /// Specialized version of `map`. + pub fn map7( + &self, + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + f: F, + ) -> Stream + where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> T, + { + self.register(OwnedMap7::new(label, t1, t2, t3, t4, t5, t6, t7, f)) + } + + /// Specialized version of `map`. + pub fn map8( + &self, + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + t8: &T8, + f: F, + ) -> Stream + where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + T: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> T, + { + self.register(OwnedMap8::new(label, t1, t2, t3, t4, t5, t6, t7, t8, f)) + } + + /// Specialized version of `map`. + pub fn map9( + &self, + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + t8: &T8, + t9: &T9, + f: F, + ) -> Stream + where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + T9: EventOutput, + T: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> T, + { + self.register(OwnedMap9::new(label, t1, t2, t3, t4, t5, t6, t7, t8, t9, f)) + } + // === AllWith === @@ -4170,6 +4336,737 @@ impl Debug for Map4Data { +// ============ +// === Map5 === +// ============ + +pub struct Map5Data { + _src1: T1, + src2: watch::Ref, + src3: watch::Ref, + src4: watch::Ref, + src5: watch::Ref, + function: F, +} +pub type OwnedMap5 = stream::Node>; +pub type Map5 = stream::WeakNode>; + +impl HasOutput for Map5Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + Out: Data, + F: 'static + Fn(&Output, &Output, &Output, &Output, &Output) -> Out, +{ + type Output = Out; +} + +impl OwnedMap5 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + Out: Data, + F: 'static + Fn(&Output, &Output, &Output, &Output, &Output) -> Out, +{ + /// Constructor. + pub fn new(label: Label, t1: &T1, t2: &T2, t3: &T3, t4: &T4, t5: &T5, function: F) -> Self { + let _src1 = t1.clone_ref(); + let src2 = watch_stream(t2); + let src3 = watch_stream(t3); + let src4 = watch_stream(t4); + let src5 = watch_stream(t5); + let def = Map5Data { _src1, src2, src3, src4, src5, function }; + let this = Self::construct(label, def); + let weak = this.downgrade(); + t1.register_target(weak.into()); + this + } +} + +impl stream::EventConsumer> + for OwnedMap5 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + Out: Data, + F: 'static + Fn(&Output, &Output, &Output, &Output, &Output) -> Out, +{ + fn on_event(&self, stack: CallStack, value1: &Output) { + let value2 = self.src2.value(); + let value3 = self.src3.value(); + let value4 = self.src4.value(); + let value5 = self.src5.value(); + let out = (self.function)(value1, &value2, &value3, &value4, &value5); + self.emit_event(stack, &out); + } +} + +impl stream::InputBehaviors for Map5Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, +{ + fn input_behaviors(&self) -> Vec { + vec![ + Link::behavior(&self.src2), + Link::behavior(&self.src3), + Link::behavior(&self.src4), + Link::behavior(&self.src5), + ] + } +} + +impl Debug for Map5Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Map5Data") + } +} + + + +// ============ +// === Map6 === +// ============ + +pub struct Map6Data { + _src1: T1, + src2: watch::Ref, + src3: watch::Ref, + src4: watch::Ref, + src5: watch::Ref, + src6: watch::Ref, + function: F, +} +pub type OwnedMap6 = stream::Node>; +pub type Map6 = stream::WeakNode>; + +impl HasOutput for Map6Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + Out: Data, + F: 'static + + Fn(&Output, &Output, &Output, &Output, &Output, &Output) -> Out, +{ + type Output = Out; +} + +impl OwnedMap6 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + Out: Data, + F: 'static + + Fn(&Output, &Output, &Output, &Output, &Output, &Output) -> Out, +{ + /// Constructor. + pub fn new( + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + function: F, + ) -> Self { + let _src1 = t1.clone_ref(); + let src2 = watch_stream(t2); + let src3 = watch_stream(t3); + let src4 = watch_stream(t4); + let src5 = watch_stream(t5); + let src6 = watch_stream(t6); + let def = Map6Data { _src1, src2, src3, src4, src5, src6, function }; + let this = Self::construct(label, def); + let weak = this.downgrade(); + t1.register_target(weak.into()); + this + } +} + +impl stream::EventConsumer> + for OwnedMap6 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + Out: Data, + F: 'static + + Fn(&Output, &Output, &Output, &Output, &Output, &Output) -> Out, +{ + fn on_event(&self, stack: CallStack, value1: &Output) { + let value2 = self.src2.value(); + let value3 = self.src3.value(); + let value4 = self.src4.value(); + let value5 = self.src5.value(); + let value6 = self.src6.value(); + let out = (self.function)(value1, &value2, &value3, &value4, &value5, &value6); + self.emit_event(stack, &out); + } +} + +impl stream::InputBehaviors for Map6Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, +{ + fn input_behaviors(&self) -> Vec { + vec![ + Link::behavior(&self.src2), + Link::behavior(&self.src3), + Link::behavior(&self.src4), + Link::behavior(&self.src5), + Link::behavior(&self.src6), + ] + } +} + +impl Debug for Map6Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Map6Data") + } +} + + + +// ============ +// === Map7 === +// ============ + +pub struct Map7Data { + _src1: T1, + src2: watch::Ref, + src3: watch::Ref, + src4: watch::Ref, + src5: watch::Ref, + src6: watch::Ref, + src7: watch::Ref, + function: F, +} +pub type OwnedMap7 = + stream::Node>; +pub type Map7 = + stream::WeakNode>; + +impl HasOutput for Map7Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + type Output = Out; +} + +impl OwnedMap7 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + /// Constructor. + pub fn new( + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + function: F, + ) -> Self { + let _src1 = t1.clone_ref(); + let src2 = watch_stream(t2); + let src3 = watch_stream(t3); + let src4 = watch_stream(t4); + let src5 = watch_stream(t5); + let src6 = watch_stream(t6); + let src7 = watch_stream(t7); + let def = Map7Data { _src1, src2, src3, src4, src5, src6, src7, function }; + let this = Self::construct(label, def); + let weak = this.downgrade(); + t1.register_target(weak.into()); + this + } +} + +impl stream::EventConsumer> + for OwnedMap7 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + fn on_event(&self, stack: CallStack, value1: &Output) { + let value2 = self.src2.value(); + let value3 = self.src3.value(); + let value4 = self.src4.value(); + let value5 = self.src5.value(); + let value6 = self.src6.value(); + let value7 = self.src7.value(); + let out = (self.function)(value1, &value2, &value3, &value4, &value5, &value6, &value7); + self.emit_event(stack, &out); + } +} + +impl stream::InputBehaviors + for Map7Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, +{ + fn input_behaviors(&self) -> Vec { + vec![ + Link::behavior(&self.src2), + Link::behavior(&self.src3), + Link::behavior(&self.src4), + Link::behavior(&self.src5), + Link::behavior(&self.src6), + Link::behavior(&self.src7), + ] + } +} + +impl Debug for Map7Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Map7Data") + } +} + + + +// ============ +// === Map8 === +// ============ + +pub struct Map8Data { + _src1: T1, + src2: watch::Ref, + src3: watch::Ref, + src4: watch::Ref, + src5: watch::Ref, + src6: watch::Ref, + src7: watch::Ref, + src8: watch::Ref, + function: F, +} +pub type OwnedMap8 = + stream::Node>; +pub type Map8 = + stream::WeakNode>; + +impl HasOutput + for Map8Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + type Output = Out; +} + +impl OwnedMap8 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + /// Constructor. + pub fn new( + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + t8: &T8, + function: F, + ) -> Self { + let _src1 = t1.clone_ref(); + let src2 = watch_stream(t2); + let src3 = watch_stream(t3); + let src4 = watch_stream(t4); + let src5 = watch_stream(t5); + let src6 = watch_stream(t6); + let src7 = watch_stream(t7); + let src8 = watch_stream(t8); + let def = Map8Data { _src1, src2, src3, src4, src5, src6, src7, src8, function }; + let this = Self::construct(label, def); + let weak = this.downgrade(); + t1.register_target(weak.into()); + this + } +} + +impl stream::EventConsumer> + for OwnedMap8 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + fn on_event(&self, stack: CallStack, value1: &Output) { + let value2 = self.src2.value(); + let value3 = self.src3.value(); + let value4 = self.src4.value(); + let value5 = self.src5.value(); + let value6 = self.src6.value(); + let value7 = self.src7.value(); + let value8 = self.src8.value(); + let out = + (self.function)(value1, &value2, &value3, &value4, &value5, &value6, &value7, &value8); + self.emit_event(stack, &out); + } +} + +impl stream::InputBehaviors + for Map8Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, +{ + fn input_behaviors(&self) -> Vec { + vec![ + Link::behavior(&self.src2), + Link::behavior(&self.src3), + Link::behavior(&self.src4), + Link::behavior(&self.src5), + Link::behavior(&self.src6), + Link::behavior(&self.src7), + Link::behavior(&self.src8), + ] + } +} + +impl Debug for Map8Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Map8Data") + } +} + + + +// ============ +// === Map8 === +// ============ + +pub struct Map9Data { + _src1: T1, + src2: watch::Ref, + src3: watch::Ref, + src4: watch::Ref, + src5: watch::Ref, + src6: watch::Ref, + src7: watch::Ref, + src8: watch::Ref, + src9: watch::Ref, + function: F, +} +pub type OwnedMap9 = + stream::Node>; +pub type Map9 = + stream::WeakNode>; + +impl HasOutput + for Map9Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + T9: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + type Output = Out; +} + +impl OwnedMap9 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + T9: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + /// Constructor. + pub fn new( + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + t8: &T8, + t9: &T9, + function: F, + ) -> Self { + let _src1 = t1.clone_ref(); + let src2 = watch_stream(t2); + let src3 = watch_stream(t3); + let src4 = watch_stream(t4); + let src5 = watch_stream(t5); + let src6 = watch_stream(t6); + let src7 = watch_stream(t7); + let src8 = watch_stream(t8); + let src9 = watch_stream(t9); + let def = Map9Data { _src1, src2, src3, src4, src5, src6, src7, src8, src9, function }; + let this = Self::construct(label, def); + let weak = this.downgrade(); + t1.register_target(weak.into()); + this + } +} + +impl stream::EventConsumer> + for OwnedMap9 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + T9: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + fn on_event(&self, stack: CallStack, value1: &Output) { + let value2 = self.src2.value(); + let value3 = self.src3.value(); + let value4 = self.src4.value(); + let value5 = self.src5.value(); + let value6 = self.src6.value(); + let value7 = self.src7.value(); + let value8 = self.src8.value(); + let value9 = self.src9.value(); + let out = (self.function)( + value1, &value2, &value3, &value4, &value5, &value6, &value7, &value8, &value9, + ); + self.emit_event(stack, &out); + } +} + +impl stream::InputBehaviors + for Map9Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T8: EventOutput, + T9: EventOutput, +{ + fn input_behaviors(&self) -> Vec { + vec![ + Link::behavior(&self.src2), + Link::behavior(&self.src3), + Link::behavior(&self.src4), + Link::behavior(&self.src5), + Link::behavior(&self.src6), + Link::behavior(&self.src7), + Link::behavior(&self.src8), + Link::behavior(&self.src9), + ] + } +} + +impl Debug + for Map9Data +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Map9Data") + } +} + + + // ================ // === AllWith2 === // ================