Passing events to sub-widgets in List Editor and refactoring of the slider component. (#6433)

This commit is contained in:
Wojciech Daniło 2023-04-27 04:42:42 +02:00 committed by GitHub
parent d0e1dd582e
commit ae94a9f40d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1478 additions and 710 deletions

22
Cargo.lock generated
View File

@ -2883,6 +2883,17 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "ensogl-example-list-view" name = "ensogl-example-list-view"
version = "0.1.0" version = "0.1.0"
@ -3008,15 +3019,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "ensogl-example-vector-editor"
version = "0.1.0"
dependencies = [
"ensogl-core",
"ensogl-hardcoded-theme",
"wasm-bindgen",
]
[[package]] [[package]]
name = "ensogl-examples" name = "ensogl-examples"
version = "0.1.0" version = "0.1.0"
@ -3034,6 +3036,7 @@ dependencies = [
"ensogl-example-focus-management", "ensogl-example-focus-management",
"ensogl-example-grid-view", "ensogl-example-grid-view",
"ensogl-example-instance-ordering", "ensogl-example-instance-ordering",
"ensogl-example-list-editor",
"ensogl-example-list-view", "ensogl-example-list-view",
"ensogl-example-mouse-events", "ensogl-example-mouse-events",
"ensogl-example-profiling-run-graph", "ensogl-example-profiling-run-graph",
@ -3043,7 +3046,6 @@ dependencies = [
"ensogl-example-sprite-system", "ensogl-example-sprite-system",
"ensogl-example-sprite-system-benchmark", "ensogl-example-sprite-system-benchmark",
"ensogl-example-text-area", "ensogl-example-text-area",
"ensogl-example-vector-editor",
] ]
[[package]] [[package]]

View File

@ -73,15 +73,11 @@
#![allow(clippy::bool_to_int_with_if)] #![allow(clippy::bool_to_int_with_if)]
#![allow(clippy::let_and_return)] #![allow(clippy::let_and_return)]
use ensogl_core::display::shape::compound::rectangle::*;
use ensogl_core::display::world::*; use ensogl_core::display::world::*;
use ensogl_core::prelude::*; use ensogl_core::prelude::*;
use ensogl_core::application::Application;
use ensogl_core::control::io::mouse; use ensogl_core::control::io::mouse;
use ensogl_core::data::color;
use ensogl_core::display; use ensogl_core::display;
use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_core::display::object::Event; use ensogl_core::display::object::Event;
use ensogl_core::display::object::ObjectOps; use ensogl_core::display::object::ObjectOps;
use ensogl_core::gui::cursor; use ensogl_core::gui::cursor;
@ -262,30 +258,50 @@ impl<T> From<StrongPlaceholder> for ItemOrPlaceholder<T> {
// === ListEditor === // === ListEditor ===
// ================== // ==================
ensogl_core::define_endpoints_2! { <T: ('static)> ensogl_core::define_endpoints_2! { <T: ('static + Debug)>
Input { Input {
/// Push a new element to the end of the list. /// Push a new element to the end of the list.
push(Weak<T>), push(Rc<RefCell<Option<T>>>),
insert((Index, Weak<T>)), /// 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<RefCell<Option<T>>>)),
/// Remove the element at the given index. If the index is invalid, nothing will happen. /// Remove the element at the given index. If the index is invalid, nothing will happen.
remove(Index), remove(Index),
/// Set the spacing between elements.
gap(f32), 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), 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), 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), 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), 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_all_insertion_points(bool),
/// Enable insertion points (plus icons) when moving mouse after the last list item.
enable_last_insertion_point(bool), enable_last_insertion_point(bool),
} }
Output { Output {
/// Fires whenever a new element was added to the list. /// Fires whenever a new element was added to the list.
on_item_added(Response<(Index, Weak<T>)>), on_item_added(Response<Index>),
on_item_removed(Response<(Index, Weak<T>)>), /// 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<RefCell<Option<T>>>)>),
/// Request new item to be inserted at the provided index. In most cases, this happens after /// 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 /// 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! { <T: ('static)>
#[derive(Derivative, CloneRef, Debug, Deref)] #[derive(Derivative, CloneRef, Debug, Deref)]
#[derivative(Clone(bound = ""))] #[derivative(Clone(bound = ""))]
pub struct ListEditor<T: 'static> { pub struct ListEditor<T: 'static + Debug> {
#[deref] #[deref]
pub frp: Frp<T>, pub frp: Frp<T>,
root: display::object::Instance, root: display::object::Instance,
@ -339,7 +355,7 @@ impl<T> From<Model<T>> for SharedModel<T> {
} }
impl<T: display::Object + CloneRef> ListEditor<T> { impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
pub fn new(cursor: &Cursor) -> Self { pub fn new(cursor: &Cursor) -> Self {
let frp = Frp::new(); let frp = Frp::new();
let model = Model::new(cursor); let model = Model::new(cursor);
@ -359,10 +375,6 @@ impl<T: display::Object + CloneRef> ListEditor<T> {
let on_move = scene.on_event::<mouse::Move>(); let on_move = scene.on_event::<mouse::Move>();
frp::extend! { network 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()); target <= on_down.map(|event| event.target());
on_up <- on_up_source.identity(); on_up <- on_up_source.identity();
@ -385,13 +397,21 @@ impl<T: display::Object + CloneRef> ListEditor<T> {
} }
self.init_add_and_remove(); 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); let (is_trashing, trash_pointer_style) = self.init_trashing(&on_up, &drag_diff);
self.init_dropping(&on_up, &pos_on_move_down, &is_trashing); 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); let insert_pointer_style = self.init_insertion_points(&on_up, &pos_on_move, &is_dragging);
frp::extend! { network frp::extend! { network
cursor.frp.set_style_override <+ all [insert_pointer_style, trash_pointer_style].fold(); 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 self
} }
@ -451,18 +471,18 @@ impl<T: display::Object + CloneRef> ListEditor<T> {
let network = self.frp.network(); let network = self.frp.network();
frp::extend! { network frp::extend! { network
push_ix <= frp.push.map(f!((item) model.push_weak(item))); push_ix <= frp.push.map(f!((item) model.push_cell(item)));
on_pushed <- frp.push.map2(&push_ix, |t, ix| Response::api((*ix, t.clone()))); on_pushed <- push_ix.map(|ix| Response::api(*ix));
frp.private.output.on_item_added <+ on_pushed; frp.private.output.on_item_added <+ on_pushed;
insert_ix <= frp.insert.map(f!(((index, item)) model.insert_weak(*index, item))); insert_ix <= frp.insert.map(f!(((index, item)) model.insert_cell(*index, item)));
on_inserted <- frp.insert.map2(&insert_ix, |t, ix| Response::api((*ix, t.1.clone()))); on_inserted <- insert_ix.map(|ix| Response::api(*ix));
frp.private.output.on_item_added <+ on_inserted; frp.private.output.on_item_added <+ on_inserted;
let on_item_removed = &frp.private.output.on_item_removed; let on_item_removed = &frp.private.output.on_item_removed;
eval frp.remove([model, on_item_removed] (index) { eval frp.remove([model, on_item_removed] (index) {
if let Some(item) = model.borrow_mut().trash_item_at(*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<T: display::Object + CloneRef> ListEditor<T> {
on_down: &frp::Stream<Event<mouse::Down>>, on_down: &frp::Stream<Event<mouse::Down>>,
target: &frp::Stream<display::object::Instance>, target: &frp::Stream<display::object::Instance>,
pos_diff: &frp::Stream<Vector2>, pos_diff: &frp::Stream<Vector2>,
) -> (frp::Stream<bool>, frp::Stream<Vector2>) { ) -> (frp::Stream<bool>, frp::Stream<Vector2>, frp::Stream<bool>) {
let model = &self.model; let model = &self.model;
let on_up = on_up.clone_ref(); let on_up = on_up.clone_ref();
let on_down = on_down.clone_ref(); let on_down = on_down.clone_ref();
@ -499,6 +519,7 @@ impl<T: display::Object + CloneRef> ListEditor<T> {
init_drag_not_disabled <- init_drag.gate_not(&drag_disabled); init_drag_not_disabled <- init_drag.gate_not(&drag_disabled);
is_dragging <- bool(&on_up, &init_drag_not_disabled).on_change(); is_dragging <- bool(&on_up, &init_drag_not_disabled).on_change();
drag_diff <- pos_diff.gate(&is_dragging); 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(); status <- bool(&on_up, &drag_diff).on_change();
start <- status.on_true(); start <- status.on_true();
@ -506,11 +527,11 @@ impl<T: display::Object + CloneRef> ListEditor<T> {
let on_item_removed = &frp.private.output.on_item_removed; let on_item_removed = &frp.private.output.on_item_removed;
eval target_on_start([model, on_item_removed] (t) { eval target_on_start([model, on_item_removed] (t) {
if let Some((index, item)) = model.borrow_mut().start_item_drag(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. /// Implementation of item trashing logic. See docs of this crate to learn more.
@ -571,8 +592,8 @@ impl<T: display::Object + CloneRef> ListEditor<T> {
let on_item_added = &frp.private.output.on_item_added; let on_item_added = &frp.private.output.on_item_added;
eval insert_index_on_drop ([model, on_item_added] (index) eval insert_index_on_drop ([model, on_item_added] (index)
if let Some((index, item)) = model.borrow_mut().place_dragged_item(*index) { if let Some(index) = model.borrow_mut().place_dragged_item(*index) {
on_item_added.emit(Response::gui((index, Rc::new(item).downgrade()))); on_item_added.emit(Response::gui(index));
} }
); );
} }
@ -591,7 +612,7 @@ impl<T: display::Object + CloneRef> ListEditor<T> {
} }
pub fn push(&self, item: T) { 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<T> { pub fn items(&self) -> Vec<T> {
@ -612,16 +633,18 @@ impl<T: display::Object + CloneRef + 'static> SharedModel<T> {
self.borrow_mut().push(item) self.borrow_mut().push(item)
} }
fn push_weak(&self, item: &Weak<T>) -> Option<Index> { fn push_cell(&self, item: &Rc<RefCell<Option<T>>>) -> Option<Index> {
item.upgrade().map(|item| self.push((*item).clone_ref())) let item = mem::take(&mut *item.borrow_mut());
item.map(|item| self.push(item))
} }
fn insert(&self, index: Index, item: T) -> Index { fn insert(&self, index: Index, item: T) -> Index {
self.borrow_mut().insert(index, item) self.borrow_mut().insert(index, item)
} }
fn insert_weak(&self, index: Index, item: &Weak<T>) -> Option<Index> { fn insert_cell(&self, index: Index, item: &Rc<RefCell<Option<T>>>) -> Option<Index> {
item.upgrade().map(|item| self.insert(index, (*item).clone_ref())) 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 { fn insert_index(&self, x: f32, center_points: &[f32]) -> ItemOrPlaceholderIndex {
@ -681,7 +704,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
/// Find an element by the provided display object reference. /// Find an element by the provided display object reference.
fn item_index_of( fn item_index_of(
&mut self, &self,
obj: &display::object::Instance, obj: &display::object::Instance,
) -> Option<(Index, ItemOrPlaceholderIndex)> { ) -> Option<(Index, ItemOrPlaceholderIndex)> {
self.items self.items
@ -841,11 +864,12 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
/// ///
/// See docs of [`Self::start_item_drag_at`] for more information. /// See docs of [`Self::start_item_drag_at`] for more information.
fn start_item_drag(&mut self, target: &display::object::Instance) -> Option<(Index, T)> { fn start_item_drag(&mut self, target: &display::object::Instance) -> Option<(Index, T)> {
let index = self.item_index_of(target); let objs = target.rev_parent_chain();
if let Some((index, index_or_placeholder_index)) = index { 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)) self.start_item_drag_at(index_or_placeholder_index).map(|item| (index, item))
} else { } else {
warn!("Requested to drag a non-existent item."); warn!("Could not find the item to drag.");
None None
} }
} }
@ -941,7 +965,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
/// Place the currently dragged element in the given index. The item will be enclosed in the /// 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 /// [`Item`] object, will handles its animation. See the documentation of
/// [`ItemOrPlaceholder`] to learn more. /// [`ItemOrPlaceholder`] to learn more.
fn place_dragged_item(&mut self, index: ItemOrPlaceholderIndex) -> Option<(Index, T)> { fn place_dragged_item(&mut self, index: ItemOrPlaceholderIndex) -> Option<Index> {
if let Some(item) = self.cursor.stop_drag_if_is::<T>() { if let Some(item) = self.cursor.stop_drag_if_is::<T>() {
self.collapse_all_placeholders_no_margin_update(); self.collapse_all_placeholders_no_margin_update();
if let Some((index, placeholder)) = self.get_indexed_merged_placeholder_at(index) { if let Some((index, placeholder)) = self.get_indexed_merged_placeholder_at(index) {
@ -958,7 +982,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
warn!("An element was inserted without a placeholder. This should not happen."); warn!("An element was inserted without a placeholder. This should not happen.");
} }
self.reposition_items(); self.reposition_items();
self.item_or_placeholder_index_to_index(index).map(|index| (index, item)) self.item_or_placeholder_index_to_index(index)
} else { } else {
warn!("Called function to insert dragged element, but no element is being dragged."); warn!("Called function to insert dragged element, but no element is being dragged.");
None None
@ -1036,7 +1060,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
} }
} }
impl<T: 'static> display::Object for ListEditor<T> { impl<T: 'static + Debug> display::Object for ListEditor<T> {
fn display_object(&self) -> &display::object::Instance { fn display_object(&self) -> &display::object::Instance {
&self.root &self.root
} }
@ -1092,92 +1116,3 @@ mod trash {
} }
use crate::placeholder::WeakPlaceholder; use crate::placeholder::WeakPlaceholder;
use trash::Trash; 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::<Rectangle>::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::<mouse::Down>();
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);
}

View File

@ -1,4 +1,11 @@
//! A slider UI component that allows adjusting a value through mouse interaction. //! 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"] #![recursion_limit = "512"]
// === Standard Linter Configuration === // === Standard Linter Configuration ===
@ -46,30 +53,30 @@ pub mod model;
// === Constants === // === 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. /// 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. /// Default upper limit of the slider value.
const MAX_VALUE_DEFAULT: f32 = 100.0; const MAX_VALUE_DEFAULT: f32 = 100.0;
/// Default for the maximum number of digits after the decimal point that is displayed. /// Default for the maximum number of digits after the decimal point that is displayed.
const MAX_DISP_DECIMAL_PLACES_DEFAULT: usize = 8; const MAX_DISP_DECIMAL_PLACES_DEFAULT: usize = 8;
/// Margin above/below the component within which vertical mouse movement will not affect slider /// Margin above/below the component within which vertical mouse movement will not affect slider
/// precision. /// resolution.
const PRECISION_ADJUSTMENT_MARGIN: f32 = 10.0; const PRECISION_ADJUSTMENT_MARGIN: f32 = 10.0;
/// The vertical mouse movement (in pixels) needed to change the slider precision by one step. /// 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 precision by one step for every /// 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 /// `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; const PRECISION_ADJUSTMENT_STEP_SIZE: f32 = 50.0;
/// The actual slider precision changes exponentially with each adjustment step. When the adjustment /// The actual slider resolution changes exponentially with each adjustment step. When the
/// is changed by one step, the slider's precision is changed to the next power of `STEP_BASE`. A /// adjustment is changed by one step, the slider's resolution is changed to the next power of
/// `STEP_BASE` of 10.0 results in the precision being powers of 10 for consecutive steps, e.g [1.0, /// `STEP_BASE`. A `STEP_BASE` of 10.0 results in the resolution being powers of 10 for consecutive
/// 10.0, 100.0, ...] when decreasing the precision and [0.1, 0.01, 0.001, ...] when increasing the /// steps, e.g [1.0, 10.0, 100.0, ...] when decreasing the resolution and [0.1, 0.01, 0.001, ...]
/// precision. /// when increasing the resolution.
const PRECISION_ADJUSTMENT_STEP_BASE: f32 = 10.0; 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; 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. /// which the pop-up is visible.
const PRECISION_ADJUSTMENT_POPUP_DURATION: f32 = 1000.0; const PRECISION_ADJUSTMENT_POPUP_DURATION: f32 = 1000.0;
/// The delay before an information tooltip is displayed when hovering over a slider component. /// 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. /// Defines which part of the slider is being dragged by the user. In case the slider allows
// #[derive(Clone, Copy, Debug, Default)] /// dragging both of its ends and the middle of the track, this struct determines which part is
// pub enum Axis2 { /// being dragged.
// #[default] #[allow(missing_docs)]
// /// The slider value is changed by dragging the slider horizontally. #[derive(Debug, Copy, Clone, Default)]
// Horizontal, pub enum DragHandle {
// /// The slider value is changed by dragging the slider vertically. Start,
// Vertical, Middle,
// }
// =================================
// === Slider position indicator ===
// =================================
/// The type of element that indicates the slider's value along its length.
#[derive(Clone, Copy, Debug, Default)]
pub enum Kind {
#[default] #[default]
/// A track is a bar that fills the slider as the value increases. The track is empty when the End,
/// 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),
} }
@ -225,16 +215,16 @@ fn value_limit_clamp(
ensogl_core::define_endpoints_2! { ensogl_core::define_endpoints_2! {
Input { 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 the color of the slider's value indicator.
set_value_indicator_color(color::Lcha), set_value_indicator_color(color::Lcha),
/// Set the color of the slider's background. /// Set the color of the slider's background.
set_background_color(color::Lcha), 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 the slider value.
set_value(f32), set_value(f32),
/// Set the default value to reset a slider to when `ctrl` + `click`-ed. /// 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_value_text_color(color::Lcha),
/// Set whether the slider's value text is hidden. /// Set whether the slider's value text is hidden.
show_value(bool), 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 /// determines by what increment the value will be changed on mouse movement. It also
/// affects the number of digits after the decimal point displayed. /// affects the number of digits after the decimal point displayed.
set_default_precision(f32), set_default_resolution(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_margin` defines a margin above/below the slider within which no /// 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), 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 /// 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_precision_adjustment_step_size(f32),
/// Set the maximum number of precision steps to prevent overflow or rounding to zero of the /// Set the maximum number of resolution steps to prevent overflow or rounding to zero of the
/// precision increments. /// resolution increments.
set_max_precision_adjustment_steps(usize), 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_precision_adjustment_disabled(bool),
/// Set the slider's label. The label will be displayed to the left of the slider's value /// Set the slider's label. The label will be displayed to the left of the slider's value
/// display. /// display.
@ -281,7 +271,7 @@ ensogl_core::define_endpoints_2! {
set_tooltip(ImString), set_tooltip(ImString),
/// Set the delay of the tooltip showing after the mouse hovers over the component. /// Set the delay of the tooltip showing after the mouse hovers over the component.
set_tooltip_delay(f32), 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. /// for which the pop-up is visible.
set_precision_popup_duration(f32), set_precision_popup_duration(f32),
/// Set whether the slider is disabled. When disabled, the slider's value cannot be changed /// 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), width(f32),
/// The component's height. /// The component's height.
height(f32), height(f32),
/// The slider's value. /// The slider track's start position.
value(f32), start_value(f32),
/// The slider's precision. /// The slider track's end position.
precision(f32), end_value(f32),
/// The slider's resolution.
resolution(f32),
/// The slider value's lower limit. This takes into account limit extension if an adaptive /// The slider value's lower limit. This takes into account limit extension if an adaptive
/// slider limit is set. /// slider limit is set.
min_value(f32), min_value(f32),
@ -319,15 +311,11 @@ ensogl_core::define_endpoints_2! {
/// slider limit is set. /// slider limit is set.
max_value(f32), max_value(f32),
/// Indicates whether the mouse is currently hovered over the component. /// Indicates whether the mouse is currently hovered over the component.
hovered(bool),
/// Indicates whether the slider is currently being dragged.
dragged(bool), dragged(bool),
/// Indicates whether the slider is disabled. /// Indicates whether the slider is disabled.
disabled(bool), disabled(bool),
/// Indicates whether the slider's value is being edited currently. /// Indicates whether the slider's value is being edited currently.
editing(bool), 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 /// 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 /// `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 /// 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. /// the mouse is moved.
#[derive(Debug, Deref, Clone)] #[derive(Debug, Deref, Clone, CloneRef)]
pub struct Slider { pub struct Slider {
/// Public FRP api of the component. /// Public FRP api of the component.
#[deref] #[deref]
@ -391,18 +379,21 @@ impl Slider {
let ptr_down_any = model.background.on_event::<mouse::Down>(); let ptr_down_any = model.background.on_event::<mouse::Down>();
let ptr_up_any = scene.on_event::<mouse::Up>(); let ptr_up_any = scene.on_event::<mouse::Up>();
let ptr_out = model.background.on_event::<mouse::Out>();
let ptr_over = model.background.on_event::<mouse::Over>();
let obj = model.display_object(); let obj = model.display_object();
frp::extend! { network frp::extend! { network
ptr_down <- ptr_down_any.map(|e| e.button() == mouse::PrimaryButton).on_true(); 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(); ptr_up <- ptr_up_any.map(|e| e.button() == mouse::PrimaryButton).on_true();
pos <- mouse.position.map( 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(&frp.set_slider_disabled);
ptr_down <- ptr_down.gate_not(&output.editing); ptr_down <- ptr_down.gate_not(&output.editing);
@ -410,35 +401,65 @@ impl Slider {
on_editing <- output.editing.on_true(); on_editing <- output.editing.on_true();
on_drag_start <- ptr_down.gate_not(&keyboard.is_control_down); on_drag_start <- ptr_down.gate_not(&keyboard.is_control_down);
on_drag_stop <- any3(&ptr_up, &on_disabled, &on_editing); 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_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_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(); 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(); prec_delta <- all_with(&drag_end, &orientation_orth, |t, d| t.get_dim(d)).on_change();
output.hovered <+ bool(&ptr_out, &ptr_over); handle <- drag_start.map9(
output.dragged <+ dragging; &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 === // === Precision calculation ===
length <- all_with(&obj.on_resized, &frp.orientation, |size, dim| size.get_dim(dim)); native_resolution <- all_with3(&length, &output.max_value, &output.min_value,
width <- all_with(&obj.on_resized, &orientation_orth, |size, dim| size.get_dim(dim)); |len, max, min| (max - min) / len
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,
}
); );
non_native_resolution <- all_with5(
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(
&width, &width,
&frp.set_precision_adjustment_margin, &frp.set_precision_adjustment_margin,
&prec_delta, &prec_delta,
@ -451,39 +472,57 @@ impl Slider {
let level = min(*max_steps as i32, (offset / step_size).ceil() as i32) * sign; let level = min(*max_steps as i32, (offset / step_size).ceil() as i32) * sign;
(level != 0).as_some_from(|| { (level != 0).as_some_from(|| {
let exp = if level > 0 { level - 1 } else { level }; let exp = if level > 0 { level - 1 } else { level };
let precision = 10.0_f32.powf(exp as f32); 10.0_f32.powf(exp as f32)
precision
}) })
} }
).on_change(); ).on_change();
precision <- all_with(&non_native_precision, &native_precision, |t,s| t.unwrap_or(*s)); resolution <- all_with(&non_native_resolution, &native_resolution, |t,s| t.unwrap_or(*s));
output.precision <+ precision; output.resolution <+ resolution;
// === Value calculation === // === Value calculation ===
value <- drag_delta1.map3(&value_on_ptr_down, &precision, values <- drag_delta1.map5(
|delta, value, precision| value + delta * precision); &handle,
value <- any2(&frp.set_value, &value); &start_value_on_ptr_down,
value <- all5( &end_value_on_ptr_down,
&value, &resolution,
&frp.set_min_value, |delta, handle, start_value, end_value, resolution| {
&frp.set_max_value, let diff = delta * resolution;
&frp.set_lower_limit_type, if let Some(handle) = handle {
&frp.set_upper_limit_type, match handle {
).map(value_limit_clamp); DragHandle::Start => (Some(start_value + diff), None),
output.value <+ value; 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 === // === Value Reset ===
reset_value <- ptr_down.gate(&keyboard.is_control_down); reset_value <- ptr_down.gate(&keyboard.is_control_down);
value_on_reset <- input.set_default_value.sample(&reset_value); value_on_reset <- input.set_default_value.sample(&reset_value);
output.value <+ value_on_reset; output.end_value <+ value_on_reset;
// === Value Animation === // === 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 frp::extend! { network
min_value <- all_with5( min_value <- all_with5(
&output.value, &output.end_value,
&input.set_min_value, &input.set_min_value,
&input.set_max_value, &input.set_max_value,
&output.min_value, &output.min_value,
@ -506,7 +545,7 @@ impl Slider {
output.min_value <+ min_value; output.min_value <+ min_value;
max_value <- all_with5( max_value <- all_with5(
&output.value, &output.end_value,
&input.set_min_value, &input.set_min_value,
&input.set_max_value, &input.set_max_value,
&output.max_value, &output.max_value,
@ -515,8 +554,8 @@ impl Slider {
).on_change(); ).on_change();
output.max_value <+ max_value; output.max_value <+ max_value;
overflow_lower <- all_with(&output.value, &min_value, |v, min| v < min).on_change(); overflow_lower <- all_with(&output.end_value, &min_value, |v, min| v < min).on_change();
overflow_upper <- all_with(&output.value, &max_value, |v, max| v > max).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_lower((v) model.set_overflow_lower_visible(*v));
eval overflow_upper((v) model.set_overflow_upper_visible(*v)); eval overflow_upper((v) model.set_overflow_upper_visible(*v));
}; };
@ -533,15 +572,15 @@ impl Slider {
frp::extend! { network frp::extend! { network
eval input.show_value((v) model.show_value(*v)); 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); default_value <- input.set_default_value.sampled_gate(&input.show_value);
is_default <- all_with(&value, &default_value, |val, def| val == def); is_default <- all_with(&value, &default_value, |val, def| val == def);
text_weight <- switch_constant(&is_default, Weight::Bold, Weight::Normal); text_weight <- switch_constant(&is_default, Weight::Bold, Weight::Normal);
eval text_weight ((v) model.set_value_text_property(*v)); 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); 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_left <- text._0();
text_right <- text._1(); text_right <- text._1();
model.value_text_left.set_content <+ text_left; 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) { fn init_precision_popup(&self) {
let network = self.frp.network(); let network = self.frp.network();
let input = &self.frp.input; let input = &self.frp.input;
@ -567,16 +606,16 @@ impl Slider {
&component_events.mouse_release_primary, &component_events.mouse_release_primary,
&component_events.mouse_down_primary &component_events.mouse_down_primary
); );
precision <- output.precision.on_change().gate(&component_drag); resolution <- output.resolution.on_change().gate(&component_drag);
model.tooltip.frp.set_style <+ precision.map(|precision| { model.tooltip.frp.set_style <+ resolution.map(|resolution| {
let prec_text = format!( 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('0');
let prec_text = prec_text.trim_end_matches('.'); let prec_text = prec_text.trim_end_matches('.');
tooltip::Style::set_label(prec_text.into()) tooltip::Style::set_label(prec_text.into())
}); });
precision_changed <- precision.constant(()); precision_changed <- resolution.constant(());
popup_anim.reset <+ precision_changed; popup_anim.reset <+ precision_changed;
popup_anim.start <+ precision_changed; popup_anim.start <+ precision_changed;
popup_hide <- any2(&popup_anim.on_end, &component_events.mouse_release_primary); popup_hide <- any2(&popup_anim.on_end, &component_events.mouse_release_primary);
@ -629,25 +668,28 @@ impl Slider {
let obj = model.display_object(); let obj = model.display_object();
frp::extend! { network 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 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; min_limit_anim.target <+ output.min_value;
max_limit_anim.target <+ output.max_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 <- all_with4(
indicator_pos <- indicator_pos.map(|(value, min, max)| (value - min) / (max - min)); &model.start_value_animation.value,
indicator_pos <- all3(&indicator_pos, &input.set_thumb_size, &input.orientation); &model.end_value_animation.value,
eval indicator_pos((v) model.set_indicator_position(v)); &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( value_text_left_pos_x <- all3(
&model.value_text_left.width, &model.value_text_left.width,
&model.value_text_dot.width, &model.value_text_dot.width,
&output.precision, &output.resolution,
); );
value_text_left_pos_x <- value_text_left_pos_x.map( 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} |(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)); eval value_text_left_pos_x((x) model.value_text_left.set_x(*x));
@ -725,8 +767,8 @@ impl Slider {
frp::extend! { network frp::extend! { network
start_editing <- input.start_value_editing.gate_not(&output.disabled); start_editing <- input.start_value_editing.gate_not(&output.disabled);
start_editing <- start_editing.gate(&input.show_value); start_editing <- start_editing.gate(&input.show_value);
value_on_edit <- output.value.sample(&start_editing); value_on_edit <- output.end_value.sample(&start_editing);
prec_on_edit <- output.precision.sample(&start_editing); prec_on_edit <- output.resolution.sample(&start_editing);
max_places_on_edit <- max_places_on_edit <-
input.set_max_disp_decimal_places.sample(&start_editing); input.set_max_disp_decimal_places.sample(&start_editing);
value_text_on_edit <- all3(&value_on_edit, &prec_on_edit, &max_places_on_edit); 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()); edit_success <- value_after_edit.map(|v| v.is_some());
value_after_edit <- value_after_edit.map(|v| v.unwrap_or_default()); 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 <- 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)); prec_after_edit <- prec_after_edit.map(|(prec, default_prec)| prec.min(*default_prec));
value_after_edit <- all5( value_after_edit <- all5(
&value_after_edit, &value_after_edit,
@ -753,19 +795,19 @@ impl Slider {
).map(value_limit_clamp); ).map(value_limit_clamp);
output.editing <+ editing; 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); value_after_edit <- value_after_edit.gate(&edit_success);
output.value <+ value_after_edit; output.end_value <+ value_after_edit;
model.value_animation.target <+ value_after_edit; model.end_value_animation.target <+ value_after_edit;
editing_event <- any2(&start_editing, &stop_editing); 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)); eval editing((t) model.set_edit_mode(t));
}; };
} }
/// Initialize the compinent with default values. /// Initialize the compinent with default values.
fn init_slider_defaults(&self) { 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_margin(PRECISION_ADJUSTMENT_MARGIN);
self.frp.set_precision_adjustment_step_size(PRECISION_ADJUSTMENT_STEP_SIZE); self.frp.set_precision_adjustment_step_size(PRECISION_ADJUSTMENT_STEP_SIZE);
self.frp.set_max_precision_adjustment_steps(MAX_PRECISION_ADJUSTMENT_STEPS); 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.frp.set_thumb_size(THUMB_SIZE_DEFAULT);
self.show_value(true); self.show_value(true);
self.orientation(Axis2::X); 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 === // === Value text formatting ===
// ============================= // =============================
/// Rounds and truncates a floating point value to a specified precision. /// Rounds and truncates a floating point value to a specified resolution.
fn value_text_truncate((value, precision, max_digits): &(f32, f32, usize)) -> String { fn value_text_truncate((value, resolution, max_digits): &(f32, f32, usize)) -> String {
if *precision < 1.0 || *max_digits == 0 { if *resolution < 1.0 || *max_digits == 0 {
let digits = (-precision.log10()).ceil() as usize; let digits = (-resolution.log10()).ceil() as usize;
let digits = digits.min(*max_digits); let digits = digits.min(*max_digits);
format!("{value:.digits$}") format!("{value:.digits$}")
} else { } 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. /// 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<ImString>) { fn display_value(
let text = value_text_truncate(&(*value, *precision, *max_digits)); value: &f32,
resolution: &f32,
max_digits: &usize,
) -> (ImString, Option<ImString>) {
let text = value_text_truncate(&(*value, *resolution, *max_digits));
let mut text_iter = text.split('.'); let mut text_iter = text.split('.');
let text_left = text_iter.next().map(|s| s.to_im_string()).unwrap_or_default(); 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()); let text_right = text_iter.next().map(|s| s.to_im_string());
(text_left, text_right) (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 { fn get_value_text_precision(text: &str) -> f32 {
let mut text_iter = text.split('.').skip(1); let mut text_iter = text.split('.').skip(1);
let text_right_len = text_iter.next().map(|t| t.len()); let text_right_len = text_iter.next().map(|t| t.len());

View File

@ -3,7 +3,6 @@
use ensogl_core::display::shape::*; use ensogl_core::display::shape::*;
use ensogl_core::prelude::*; use ensogl_core::prelude::*;
use crate::Kind;
use crate::LabelPosition; use crate::LabelPosition;
use ensogl_core::application::Application; use ensogl_core::application::Application;
@ -21,8 +20,6 @@ use ensogl_tooltip::Tooltip;
// === Constants === // === 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. /// Default component width on initialization.
const COMPONENT_WIDTH_DEFAULT: f32 = 200.0; const COMPONENT_WIDTH_DEFAULT: f32 = 200.0;
/// Default component height on initialization. /// Default component height on initialization.
@ -47,8 +44,6 @@ impl Background {
fn new() -> Self { fn new() -> Self {
let width: Var<Pixels> = "input_size.x".into(); let width: Var<Pixels> = "input_size.x".into();
let height: Var<Pixels> = "input_size.y".into(); let height: Var<Pixels> = "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 = Rect((&width, &height)).corners_radius(&height / 2.0);
let shape = shape.into(); let shape = shape.into();
Background { width, height, shape } Background { width, height, shape }
@ -72,62 +67,33 @@ mod background {
/// Track shape that fills the slider proportional to the slider value. /// Track shape that fills the slider proportional to the slider value.
mod track { mod track {
use super::*; use super::*;
ensogl_core::shape! { ensogl_core::shape! {
above = [background]; above = [background];
pointer_events = false; pointer_events = false;
alignment = center; 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 Background{width,height,shape: background} = Background::new();
let track = Rect(( let length = &end - &start;
&width * &slider_fraction_horizontal, let track = Rect((&width * &length, &height));
&height * &slider_fraction_vertical, let track = track.translate_x(&width * (length - 1.0) * 0.5 + &width * start);
));
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 track = track.intersection(background).fill(color); let track = track.intersection(background).fill(color);
track.into() 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. /// Triangle shape used as an overflow indicator on either side of the range.
mod overflow { mod overflow {
use super::*; use super::*;
ensogl_core::shape! { ensogl_core::shape! {
above = [background, track, thumb]; above = [background, track];
pointer_events = false; pointer_events = false;
alignment = center; alignment = center;
(style:Style, color:Vector4) { (style:Style, color:Vector4) {
let width: Var<Pixels> = "input_size.x".into(); let width: Var<Pixels> = "input_size.x".into();
let height: Var<Pixels> = "input_size.y".into(); let height: Var<Pixels> = "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 color = style.get_color(theme::overflow::color);
let triangle = Triangle(width, height); let triangle = Triangle(width, height);
@ -148,70 +114,66 @@ mod overflow {
#[derive(Debug)] #[derive(Debug)]
pub struct Model { pub struct Model {
/// Background element /// Background element
pub background: background::View, pub background: background::View,
/// Slider track element that fills the slider proportional to the slider value. /// Slider track element that fills the slider proportional to the slider value.
pub track: track::View, pub track: track::View,
/// Slider thumb element that moves across the slider proportional to the slider value.
pub thumb: thumb::View,
/// Indicator for overflow when the value is below the lower limit. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// Tooltip component showing either a tooltip message or slider precision changes.
pub tooltip: Tooltip, pub tooltip: Tooltip,
/// Animation component that smoothly adjusts the slider value on large jumps. /// Animation component that smoothly adjusts the slider start value on large jumps.
pub value_animation: Animation<f32>, pub start_value_animation: Animation<f32>,
/// Animation component that smoothly adjusts the slider end value on large jumps.
pub end_value_animation: Animation<f32>,
/// Root of the display object. /// 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 { impl Model {
/// Create a new slider model. /// Create a new slider model.
pub fn new(app: &Application, frp_network: &frp::Network) -> Self { pub fn new(app: &Application, frp_network: &frp::Network) -> Self {
let root = display::object::Instance::new(); let root = display::object::Instance::new();
let value = display::object::Instance::new();
let label = app.new_view::<text::Text>(); let label = app.new_view::<text::Text>();
let value_text_left = app.new_view::<text::Text>(); let value_text_left = app.new_view::<text::Text>();
let value_text_dot = app.new_view::<text::Text>(); let value_text_dot = app.new_view::<text::Text>();
let value_text_right = app.new_view::<text::Text>(); let value_text_right = app.new_view::<text::Text>();
let value_text_edit = app.new_view::<text::Text>(); let value_text_edit = app.new_view::<text::Text>();
let tooltip = Tooltip::new(app); 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 background = background::View::new();
let track = track::View::new(); let track = track::View::new();
let thumb = thumb::View::new();
let overflow_lower = overflow::View::new(); let overflow_lower = overflow::View::new();
let overflow_upper = 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); let style = StyleWatch::new(&app.display.default_scene.style_sheet);
root.add_child(&background); root.add_child(&background);
root.add_child(&track); root.add_child(&track);
root.add_child(&label); root.add_child(&label);
root.add_child(&value_text_left); root.add_child(&value);
root.add_child(&value_text_dot); value.add_child(&value_text_left);
root.add_child(&value_text_right); value.add_child(&value_text_dot);
value.add_child(&value_text_right);
app.display.default_scene.add_child(&tooltip); 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 { let model = Self {
background, background,
track, track,
thumb,
overflow_lower, overflow_lower,
overflow_upper, overflow_upper,
label, label,
@ -220,8 +182,10 @@ impl Model {
value_text_right, value_text_right,
value_text_edit, value_text_edit,
tooltip, tooltip,
value_animation, start_value_animation,
end_value_animation,
root, root,
value,
}; };
model.init(style) model.init(style)
} }
@ -237,7 +201,6 @@ impl Model {
self.label.set_font(text::font::DEFAULT_FONT); self.label.set_font(text::font::DEFAULT_FONT);
self.background.color.set(background_color.into()); self.background.color.set(background_color.into());
self.track.color.set(track_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.update_size(Vector2(COMPONENT_WIDTH_DEFAULT, COMPONENT_HEIGHT_DEFAULT));
self.value_text_dot.set_content("."); self.value_text_dot.set_content(".");
self self
@ -245,16 +208,16 @@ impl Model {
/// Set the component size. /// Set the component size.
pub fn update_size(&self, size: Vector2<f32>) { pub fn update_size(&self, size: Vector2<f32>) {
let margin = Vector2(COMPONENT_MARGIN * 2.0, COMPONENT_MARGIN * 2.0); self.background.set_size(size);
self.background.set_size(size + margin); self.track.set_size(size);
self.track.set_size(size + margin); self.background.set_x(size.x / 2.0);
self.thumb.set_size(size + margin); 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. /// Set the color of the slider track or thumb.
pub fn set_indicator_color(&self, color: &color::Lcha) { pub fn set_indicator_color(&self, color: &color::Lcha) {
self.track.color.set(color::Rgba::from(color).into()); 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. /// Set the color of the slider background.
@ -262,43 +225,22 @@ impl Model {
self.background.color.set(color::Rgba::from(color).into()); 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. /// Set the position of the value indicator.
pub fn set_indicator_position(&self, (fraction, size, orientation): &(f32, f32, Axis2)) { pub fn set_indicator_position(&self, start: f32, fraction: f32, orientation: Axis2) {
self.thumb.slider_fraction.set(*fraction);
match orientation { match orientation {
Axis2::X => { Axis2::X => {
self.track.slider_fraction_horizontal.set(fraction.clamp(0.0, 1.0)); self.track.start.set(start.clamp(0.0, 1.0));
self.track.slider_fraction_vertical.set(1.0); self.track.end.set(fraction.clamp(0.0, 1.0));
self.thumb.thumb_width.set(*size);
self.thumb.thumb_height.set(1.0);
} }
Axis2::Y => { Axis2::Y => {
self.track.slider_fraction_horizontal.set(1.0); self.track.end.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);
} }
} }
} }
/// Set the size and orientation of the overflow markers. /// Set the size and orientation of the overflow markers.
pub fn set_overflow_marker_shape(&self, (size, orientation): &(f32, Axis2)) { 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;
let size = Vector2(*size, *size) * OVERFLOW_MARKER_SIZE + margin;
self.overflow_lower.set_size(size); self.overflow_lower.set_size(size);
self.overflow_upper.set_size(size); self.overflow_upper.set_size(size);
match orientation { match orientation {
@ -386,13 +328,9 @@ impl Model {
/// Set whether the slider value text is hidden. /// Set whether the slider value text is hidden.
pub fn show_value(&self, visible: bool) { pub fn show_value(&self, visible: bool) {
if visible { if visible {
self.root.add_child(&self.value_text_left); self.root.add_child(&self.value);
self.root.add_child(&self.value_text_dot);
self.root.add_child(&self.value_text_right);
} else { } else {
self.root.remove_child(&self.value_text_left); self.root.remove_child(&self.value);
self.root.remove_child(&self.value_text_dot);
self.root.remove_child(&self.value_text_right);
} }
} }
@ -407,21 +345,19 @@ impl Model {
/// Set whether the value is being edited. This hides the value display and shows a text editor /// Set whether the value is being edited. This hides the value display and shows a text editor
/// field to enter a new value. /// 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 { if *editing {
self.root.remove_child(&self.value_text_left); self.root.remove_child(&self.value);
self.root.remove_child(&self.value_text_dot);
self.root.remove_child(&self.value_text_right);
self.root.add_child(&self.value_text_edit); self.root.add_child(&self.value_text_edit);
self.value_text_edit.deprecated_focus(); self.value_text_edit.deprecated_focus();
self.value_text_edit.add_cursor_at_front(); self.value_text_edit.add_cursor_at_front();
self.value_text_edit.cursor_select_to_text_end(); self.value_text_edit.cursor_select_to_text_end();
} else { } else {
self.root.add_child(&self.value_text_left); self.root.add_child(&self.value);
if *precision < 1.0 { // if *precision < 1.0 {
self.root.add_child(&self.value_text_dot); // self.root.add_child(&self.value_text_dot);
self.root.add_child(&self.value_text_right); // self.root.add_child(&self.value_text_right);
} // }
self.root.remove_child(&self.value_text_edit); self.root.remove_child(&self.value_text_edit);
self.value_text_edit.deprecated_defocus(); self.value_text_edit.deprecated_defocus();
self.value_text_edit.remove_all_cursors(); 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. /// 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) { pub fn set_value_text_right_visible(&self, enabled: bool) {
if enabled { if enabled {
self.root.add_child(&self.value_text_dot); self.value.add_child(&self.value_text_dot);
self.root.add_child(&self.value_text_right); self.value.add_child(&self.value_text_right);
} else { } else {
self.root.remove_child(&self.value_text_dot); self.value.remove_child(&self.value_text_dot);
self.root.remove_child(&self.value_text_right); self.value.remove_child(&self.value_text_right);
} }
} }

View File

@ -37,12 +37,13 @@ pub enum State {
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug)]
pub struct SomeEvent { pub struct SomeEvent {
pub data: frp::AnyData, pub data: frp::AnyData,
state: Rc<Cell<State>>, state: Rc<Cell<State>>,
current_target: Rc<RefCell<Option<WeakInstance>>>,
/// Indicates whether the event participates in the capturing phase. /// Indicates whether the event participates in the capturing phase.
pub captures: Rc<Cell<bool>>, pub captures: Rc<Cell<bool>>,
/// Indicates whether the event participates in the bubbling phase. /// Indicates whether the event participates in the bubbling phase.
pub bubbles: Rc<Cell<bool>>, pub bubbles: Rc<Cell<bool>>,
} }
impl SomeEvent { impl SomeEvent {
@ -50,9 +51,10 @@ impl SomeEvent {
pub fn new<T: 'static>(target: Option<WeakInstance>, payload: T) -> Self { pub fn new<T: 'static>(target: Option<WeakInstance>, payload: T) -> Self {
let event = Event::new(target, payload); let event = Event::new(target, payload);
let state = event.state.clone_ref(); let state = event.state.clone_ref();
let current_target = event.current_target.clone_ref();
let captures = Rc::new(Cell::new(true)); let captures = Rc::new(Cell::new(true));
let bubbles = 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. /// The [`State]` of the event.
@ -69,6 +71,12 @@ impl SomeEvent {
pub fn set_bubbling(&self, value: bool) { pub fn set_bubbling(&self, value: bool) {
self.bubbles.set(value); 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 { impl Default for SomeEvent {
@ -113,9 +121,10 @@ impl<T: Debug> Debug for Event<T> {
#[derivative(Default(bound = "T: Default"))] #[derivative(Default(bound = "T: Default"))]
pub struct EventData<T> { pub struct EventData<T> {
#[deref] #[deref]
pub payload: T, pub payload: T,
target: Option<WeakInstance>, target: Option<WeakInstance>,
state: Rc<Cell<State>>, current_target: Rc<RefCell<Option<WeakInstance>>>,
state: Rc<Cell<State>>,
} }
impl<T: Debug> Debug for EventData<T> { impl<T: Debug> Debug for EventData<T> {
@ -130,7 +139,8 @@ impl<T: Debug> Debug for EventData<T> {
impl<T> Event<T> { impl<T> Event<T> {
fn new(target: Option<WeakInstance>, payload: T) -> Self { fn new(target: Option<WeakInstance>, payload: T) -> Self {
let state = default(); 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 } Self { data }
} }
@ -152,6 +162,18 @@ impl<T> Event<T> {
pub fn target(&self) -> Option<Instance> { pub fn target(&self) -> Option<Instance> {
self.data.target.as_ref().and_then(|t| t.upgrade()) 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<Instance> {
self.data.current_target.borrow().as_ref().and_then(|t| t.upgrade())
}
} }

View File

@ -2217,7 +2217,7 @@ impl InstanceDef {
/// Get reversed parent chain of this display object (`[root, child_of root, ..., parent, /// Get reversed parent chain of this display object (`[root, child_of root, ..., parent,
/// self]`). The last item is this object. /// self]`). The last item is this object.
fn rev_parent_chain(&self) -> Vec<Instance> { pub fn rev_parent_chain(&self) -> Vec<Instance> {
let mut vec = default(); let mut vec = default();
Self::build_rev_parent_chain(&mut vec, Some(self.clone_ref().into())); Self::build_rev_parent_chain(&mut vec, Some(self.clone_ref().into()));
vec vec
@ -2415,6 +2415,7 @@ impl InstanceDef {
if event.captures.get() { if event.captures.get() {
for object in &rev_parent_chain { for object in &rev_parent_chain {
if !event.is_cancelled() { if !event.is_cancelled() {
event.set_current_target(Some(object));
object.event.capturing_fan.emit(&event.data); object.event.capturing_fan.emit(&event.data);
} else { } else {
break; break;
@ -2430,12 +2431,14 @@ impl InstanceDef {
if event.bubbles.get() { if event.bubbles.get() {
for object in rev_parent_chain.iter().rev() { for object in rev_parent_chain.iter().rev() {
if !event.is_cancelled() { if !event.is_cancelled() {
event.set_current_target(Some(object));
object.event.bubbling_fan.emit(&event.data); object.event.bubbling_fan.emit(&event.data);
} else { } else {
break; break;
} }
} }
} }
event.set_current_target(None);
} }
fn new_event<T>(&self, payload: T) -> event::SomeEvent fn new_event<T>(&self, payload: T) -> event::SomeEvent

View File

@ -1024,15 +1024,21 @@ impl SceneData {
let layer = object.display_layer(); let layer = object.display_layer();
let camera = layer.map_or(self.camera(), |l| l.camera()); let camera = layer.map_or(self.camera(), |l| l.camera());
let origin_clip_space = camera.view_projection_matrix() * origin_world_space; let origin_clip_space = camera.view_projection_matrix() * origin_world_space;
let inv_object_matrix = object.transformation_matrix().try_inverse().unwrap(); if let Some(inv_object_matrix) = object.transformation_matrix().try_inverse() {
let shape = camera.screen();
let shape = camera.screen(); let clip_space_z = origin_clip_space.z;
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_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_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 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;
let world_space = camera.inversed_view_projection_matrix() * clip_space; (inv_object_matrix * world_space).xy()
(inv_object_matrix * world_space).xy() } else {
warn!(
"The object transformation matrix is not invertible, \
this can cause visual artifacts."
);
default()
}
} }
} }

View File

@ -30,4 +30,4 @@ ensogl-example-slider = { path = "slider" }
ensogl-example-sprite-system = { path = "sprite-system" } ensogl-example-sprite-system = { path = "sprite-system" }
ensogl-example-sprite-system-benchmark = { path = "sprite-system-benchmark" } ensogl-example-sprite-system-benchmark = { path = "sprite-system-benchmark" }
ensogl-example-text-area = { path = "text-area" } ensogl-example-text-area = { path = "text-area" }
ensogl-example-vector-editor = { path = "vector-editor" } ensogl-example-list-editor = { path = "list-editor" }

View File

@ -1,5 +1,5 @@
[package] [package]
name = "ensogl-example-vector-editor" name = "ensogl-example-list-editor"
version = "0.1.0" version = "0.1.0"
authors = ["Enso Team <contact@enso.org>"] authors = ["Enso Team <contact@enso.org>"]
edition = "2021" edition = "2021"
@ -8,9 +8,11 @@ edition = "2021"
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
enso-frp = { path = "../../../frp" }
ensogl-core = { path = "../../core" } ensogl-core = { path = "../../core" }
wasm-bindgen = { workspace = true } ensogl-list-editor = { path = "../../component/list-editor" }
ensogl-hardcoded-theme = { path = "../../../ensogl/app/theme/hardcoded" } 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. # 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] [package.metadata.wasm-pack.profile.release]

View File

@ -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::<slider::Slider>();
slider1.set_size((200.0, 24.0));
let slider2 = app.new_view::<slider::Slider>();
slider2.set_size((200.0, 24.0));
let slider3 = app.new_view::<slider::Slider>();
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::Slider>();
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);
}

View File

@ -1,4 +1,4 @@
//! A debug scene which shows the slider component //! An example scene showing the slider component usage.
// === Features === // === Features ===
#![feature(associated_type_defaults)] #![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::Slider>();
// 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 === // === Model definition ===
// ======================== // ========================
@ -81,7 +65,7 @@ impl Model {
/// Add example sliders to scene. /// Add example sliders to scene.
fn init_sliders(&self) { fn init_sliders(&self) {
let slider1 = make_slider(&self.app); let slider1 = self.app.new_view::<slider::Slider>();
slider1.set_size((200.0, 24.0)); slider1.set_size((200.0, 24.0));
slider1.set_y(-120.0); slider1.set_y(-120.0);
slider1.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.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.root.add_child(&slider1);
self.sliders.borrow_mut().push(slider1); self.sliders.borrow_mut().push(slider1);
let slider2 = make_slider(&self.app); // # IMPORTANT
slider2.set_size((400.0, 50.0)); // This code is commented because the slider implementation is not finished yet. Please
slider2.set_y(-60.0); // refer to the doc comments in the slider's module to learn more.
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)); // let slider2 = self.app.new_view::<slider::Slider>();
slider3.set_y(0.0); // slider2.set_size((400.0, 50.0));
slider3.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider2.set_y(-60.0);
slider3.frp.set_default_value(100.0); // slider2.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider3.frp.set_value(100.0); // slider2.frp.set_slider_disabled(true);
slider3.frp.set_max_value(500.0); // slider2.frp.set_label("Disabled");
slider3.frp.set_label("Adaptive lower limit"); // self.root.add_child(&slider2);
slider3.frp.set_lower_limit_type(slider::SliderLimit::Adaptive); // self.sliders.borrow_mut().push(slider2);
self.root.add_child(&slider3); //
self.sliders.borrow_mut().push(slider3); // let slider3 = self.app.new_view::<slider::Slider>();
// slider3.set_size((400.0, 50.0));
let slider4 = make_slider(&self.app); // slider3.set_y(0.0);
slider4.set_size((400.0, 50.0)); // slider3.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider4.set_y(60.0); // slider3.frp.set_default_value(100.0);
slider4.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider3.frp.set_value(100.0);
slider4.frp.set_label("Adaptive upper limit"); // slider3.frp.set_max_value(500.0);
slider4.frp.set_label_position(slider::LabelPosition::Inside); // slider3.frp.set_label("Adaptive lower limit");
slider4.frp.set_upper_limit_type(slider::SliderLimit::Adaptive); // slider3.frp.set_lower_limit_type(slider::SliderLimit::Adaptive);
self.root.add_child(&slider4); // self.root.add_child(&slider3);
self.sliders.borrow_mut().push(slider4); // self.sliders.borrow_mut().push(slider3);
//
let slider5 = make_slider(&self.app); // let slider4 = self.app.new_view::<slider::Slider>();
slider5.set_size((75.0, 230.0)); // slider4.set_size((400.0, 50.0));
slider5.set_y(-35.0); // slider4.set_y(60.0);
slider5.set_x(275.0); // slider4.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider5.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider4.frp.set_label("Adaptive upper limit");
slider5.frp.set_label("Hard limits"); // slider4.frp.set_label_position(slider::LabelPosition::Inside);
slider5.frp.orientation(Axis2::Y); // slider4.frp.set_upper_limit_type(slider::SliderLimit::Adaptive);
slider5.frp.set_max_disp_decimal_places(4); // self.root.add_child(&slider4);
self.root.add_child(&slider5); // self.sliders.borrow_mut().push(slider4);
self.sliders.borrow_mut().push(slider5); //
// let slider5 = self.app.new_view::<slider::Slider>();
let slider6 = make_slider(&self.app); // slider5.set_size((75.0, 230.0));
slider6.set_size((75.0, 230.0)); // slider5.set_y(-35.0);
slider6.set_y(-35.0); // slider5.set_x(275.0);
slider6.set_x(375.0); // slider5.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider6.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider5.frp.set_label("Hard limits");
slider6.frp.set_label("Soft\nlimits"); // slider5.frp.orientation(Axis2::Y);
slider6.frp.set_label_position(slider::LabelPosition::Inside); // slider5.frp.set_max_disp_decimal_places(4);
slider6.frp.set_lower_limit_type(slider::SliderLimit::Soft); // self.root.add_child(&slider5);
slider6.frp.set_upper_limit_type(slider::SliderLimit::Soft); // self.sliders.borrow_mut().push(slider5);
slider6.frp.orientation(Axis2::Y); //
slider6.frp.set_max_disp_decimal_places(4); // let slider6 = self.app.new_view::<slider::Slider>();
self.root.add_child(&slider6); // slider6.set_size((75.0, 230.0));
self.sliders.borrow_mut().push(slider6); // slider6.set_y(-35.0);
// slider6.set_x(375.0);
let slider7 = make_slider(&self.app); // slider6.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider7.set_size((400.0, 10.0)); // slider6.frp.set_label("Soft\nlimits");
slider7.set_y(-160.0); // slider6.frp.set_label_position(slider::LabelPosition::Inside);
slider7.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider6.frp.set_lower_limit_type(slider::SliderLimit::Soft);
slider7.frp.show_value(false); // slider6.frp.set_upper_limit_type(slider::SliderLimit::Soft);
slider7.frp.set_precision_adjustment_disabled(true); // slider6.frp.orientation(Axis2::Y);
slider7.frp.kind(slider::Kind::Scrollbar(0.1)); // slider6.frp.set_max_disp_decimal_places(4);
slider7.frp.set_thumb_size(0.1); // self.root.add_child(&slider6);
self.root.add_child(&slider7); // self.sliders.borrow_mut().push(slider6);
self.sliders.borrow_mut().push(slider7); //
// let slider7 = self.app.new_view::<slider::Slider>();
let slider8 = make_slider(&self.app); // slider7.set_size((400.0, 10.0));
slider8.set_size((400.0, 10.0)); // slider7.set_y(-160.0);
slider8.set_y(-180.0); // slider7.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider8.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider7.frp.show_value(false);
slider8.frp.show_value(false); // slider7.frp.set_precision_adjustment_disabled(true);
slider8.frp.set_precision_adjustment_disabled(true); // slider7.frp.kind(slider::Kind::Scrollbar(0.1));
slider8.frp.kind(slider::Kind::Scrollbar(0.25)); // slider7.frp.set_thumb_size(0.1);
slider8.frp.set_thumb_size(0.25); // self.root.add_child(&slider7);
self.root.add_child(&slider8); // self.sliders.borrow_mut().push(slider7);
self.sliders.borrow_mut().push(slider8); //
// let slider8 = self.app.new_view::<slider::Slider>();
let slider9 = make_slider(&self.app); // slider8.set_size((400.0, 10.0));
slider9.set_size((400.0, 10.0)); // slider8.set_y(-180.0);
slider9.set_y(-200.0); // slider8.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider9.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider8.frp.show_value(false);
slider9.frp.show_value(false); // slider8.frp.set_precision_adjustment_disabled(true);
slider9.frp.set_precision_adjustment_disabled(true); // slider8.frp.kind(slider::Kind::Scrollbar(0.25));
slider9.frp.kind(slider::Kind::Scrollbar(0.5)); // slider8.frp.set_thumb_size(0.25);
slider9.frp.set_thumb_size(0.5); // self.root.add_child(&slider8);
self.root.add_child(&slider9); // self.sliders.borrow_mut().push(slider8);
self.sliders.borrow_mut().push(slider9); //
// let slider9 = self.app.new_view::<slider::Slider>();
let slider10 = make_slider(&self.app); // slider9.set_size((400.0, 10.0));
slider10.set_size((10.0, 230)); // slider9.set_y(-200.0);
slider10.set_y(-35.0); // slider9.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0));
slider10.set_x(430.0); // slider9.frp.show_value(false);
slider10.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); // slider9.frp.set_precision_adjustment_disabled(true);
slider10.frp.show_value(false); // slider9.frp.kind(slider::Kind::Scrollbar(0.5));
slider10.frp.set_precision_adjustment_disabled(true); // slider9.frp.set_thumb_size(0.5);
slider10.frp.kind(slider::Kind::Scrollbar(0.1)); // self.root.add_child(&slider9);
slider10.frp.orientation(Axis2::Y); // self.sliders.borrow_mut().push(slider9);
self.root.add_child(&slider10); //
self.sliders.borrow_mut().push(slider10); // let slider10 = self.app.new_view::<slider::Slider>();
// 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. /// Drop all sliders from scene.

View File

@ -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_focus_management as focus_management;
pub use ensogl_example_grid_view as grid_view; pub use ensogl_example_grid_view as grid_view;
pub use ensogl_example_instance_ordering as instance_ordering; 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_list_view as list_view;
pub use ensogl_example_mouse_events as mouse_events; pub use ensogl_example_mouse_events as mouse_events;
pub use ensogl_example_profiling_run_graph as profiling_run_graph; pub use ensogl_example_profiling_run_graph as profiling_run_graph;

View File

@ -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<T> {
#[deref]
pub frp: Frp,
display_object: display::object::Instance,
model: Rc<RefCell<Model<T>>>,
}
#[derive(Debug, Derivative)]
#[derivative(Default(bound = ""))]
pub struct Model<T> {
items: Vec<T>,
}
impl<T> VectorEditor<T> {
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::<mouse::Up>();
frp::extend! { network
eval_ event_handler ([] {
warn!("Mouse up in parent");
});
}
self
}
}
impl<T: display::Object> VectorEditor<T> {
fn append(&self, item: T) {
self.add_child(&item);
self.model.borrow_mut().items.push(item);
}
}
impl<T> display::Object for VectorEditor<T> {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
impl<T> Default for VectorEditor<T> {
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::<Rectangle>::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::<mouse::Over>();
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);
}

View File

@ -1056,6 +1056,172 @@ impl Network {
self.register(OwnedMap4::new(label, t1, t2, t3, t4, f)) self.register(OwnedMap4::new(label, t1, t2, t3, t4, f))
} }
/// Specialized version of `map`.
pub fn map5<T1, T2, T3, T4, T5, F, T>(
&self,
label: Label,
t1: &T1,
t2: &T2,
t3: &T3,
t4: &T4,
t5: &T5,
f: F,
) -> Stream<T>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T: Data,
F: 'static + Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>) -> T,
{
self.register(OwnedMap5::new(label, t1, t2, t3, t4, t5, f))
}
/// Specialized version of `map`.
pub fn map6<T1, T2, T3, T4, T5, T6, F, T>(
&self,
label: Label,
t1: &T1,
t2: &T2,
t3: &T3,
t4: &T4,
t5: &T5,
t6: &T6,
f: F,
) -> Stream<T>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T: Data,
F: 'static
+ Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>, &Output<T6>) -> T,
{
self.register(OwnedMap6::new(label, t1, t2, t3, t4, t5, t6, f))
}
/// Specialized version of `map`.
pub fn map7<T1, T2, T3, T4, T5, T6, T7, F, T>(
&self,
label: Label,
t1: &T1,
t2: &T2,
t3: &T3,
t4: &T4,
t5: &T5,
t6: &T6,
t7: &T7,
f: F,
) -> Stream<T>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
T: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
) -> T,
{
self.register(OwnedMap7::new(label, t1, t2, t3, t4, t5, t6, t7, f))
}
/// Specialized version of `map`.
pub fn map8<T1, T2, T3, T4, T5, T6, T7, T8, F, T>(
&self,
label: Label,
t1: &T1,
t2: &T2,
t3: &T3,
t4: &T4,
t5: &T5,
t6: &T6,
t7: &T7,
t8: &T8,
f: F,
) -> Stream<T>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
T8: EventOutput,
T: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
) -> T,
{
self.register(OwnedMap8::new(label, t1, t2, t3, t4, t5, t6, t7, t8, f))
}
/// Specialized version of `map`.
pub fn map9<T1, T2, T3, T4, T5, T6, T7, T8, T9, F, T>(
&self,
label: Label,
t1: &T1,
t2: &T2,
t3: &T3,
t4: &T4,
t5: &T5,
t6: &T6,
t7: &T7,
t8: &T8,
t9: &T9,
f: F,
) -> Stream<T>
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<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
&Output<T9>,
) -> T,
{
self.register(OwnedMap9::new(label, t1, t2, t3, t4, t5, t6, t7, t8, t9, f))
}
// === AllWith === // === AllWith ===
@ -4170,6 +4336,737 @@ impl<T1, T2, T3, T4, F> Debug for Map4Data<T1, T2, T3, T4, F> {
// ============
// === Map5 ===
// ============
pub struct Map5Data<T1, T2, T3, T4, T5, F> {
_src1: T1,
src2: watch::Ref<T2>,
src3: watch::Ref<T3>,
src4: watch::Ref<T4>,
src5: watch::Ref<T5>,
function: F,
}
pub type OwnedMap5<T1, T2, T3, T4, T5, F> = stream::Node<Map5Data<T1, T2, T3, T4, T5, F>>;
pub type Map5<T1, T2, T3, T4, T5, F> = stream::WeakNode<Map5Data<T1, T2, T3, T4, T5, F>>;
impl<T1, T2, T3, T4, T5, F, Out> HasOutput for Map5Data<T1, T2, T3, T4, T5, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
Out: Data,
F: 'static + Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>) -> Out,
{
type Output = Out;
}
impl<T1, T2, T3, T4, T5, F, Out> OwnedMap5<T1, T2, T3, T4, T5, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
Out: Data,
F: 'static + Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>) -> 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<T1, T2, T3, T4, T5, F, Out> stream::EventConsumer<Output<T1>>
for OwnedMap5<T1, T2, T3, T4, T5, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
Out: Data,
F: 'static + Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>) -> Out,
{
fn on_event(&self, stack: CallStack, value1: &Output<T1>) {
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<T1, T2, T3, T4, T5, F> stream::InputBehaviors for Map5Data<T1, T2, T3, T4, T5, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
{
fn input_behaviors(&self) -> Vec<Link> {
vec![
Link::behavior(&self.src2),
Link::behavior(&self.src3),
Link::behavior(&self.src4),
Link::behavior(&self.src5),
]
}
}
impl<T1, T2, T3, T4, T5, F> Debug for Map5Data<T1, T2, T3, T4, T5, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Map5Data")
}
}
// ============
// === Map6 ===
// ============
pub struct Map6Data<T1, T2, T3, T4, T5, T6, F> {
_src1: T1,
src2: watch::Ref<T2>,
src3: watch::Ref<T3>,
src4: watch::Ref<T4>,
src5: watch::Ref<T5>,
src6: watch::Ref<T6>,
function: F,
}
pub type OwnedMap6<T1, T2, T3, T4, T5, T6, F> = stream::Node<Map6Data<T1, T2, T3, T4, T5, T6, F>>;
pub type Map6<T1, T2, T3, T4, T5, T6, F> = stream::WeakNode<Map6Data<T1, T2, T3, T4, T5, T6, F>>;
impl<T1, T2, T3, T4, T5, T6, F, Out> HasOutput for Map6Data<T1, T2, T3, T4, T5, T6, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
Out: Data,
F: 'static
+ Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>, &Output<T6>) -> Out,
{
type Output = Out;
}
impl<T1, T2, T3, T4, T5, T6, F, Out> OwnedMap6<T1, T2, T3, T4, T5, T6, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
Out: Data,
F: 'static
+ Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>, &Output<T6>) -> 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<T1, T2, T3, T4, T5, T6, F, Out> stream::EventConsumer<Output<T1>>
for OwnedMap6<T1, T2, T3, T4, T5, T6, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
Out: Data,
F: 'static
+ Fn(&Output<T1>, &Output<T2>, &Output<T3>, &Output<T4>, &Output<T5>, &Output<T6>) -> Out,
{
fn on_event(&self, stack: CallStack, value1: &Output<T1>) {
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<T1, T2, T3, T4, T5, T6, F> stream::InputBehaviors for Map6Data<T1, T2, T3, T4, T5, T6, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
{
fn input_behaviors(&self) -> Vec<Link> {
vec![
Link::behavior(&self.src2),
Link::behavior(&self.src3),
Link::behavior(&self.src4),
Link::behavior(&self.src5),
Link::behavior(&self.src6),
]
}
}
impl<T1, T2, T3, T4, T5, T6, F> Debug for Map6Data<T1, T2, T3, T4, T5, T6, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Map6Data")
}
}
// ============
// === Map7 ===
// ============
pub struct Map7Data<T1, T2, T3, T4, T5, T6, T7, F> {
_src1: T1,
src2: watch::Ref<T2>,
src3: watch::Ref<T3>,
src4: watch::Ref<T4>,
src5: watch::Ref<T5>,
src6: watch::Ref<T6>,
src7: watch::Ref<T7>,
function: F,
}
pub type OwnedMap7<T1, T2, T3, T4, T5, T6, T7, F> =
stream::Node<Map7Data<T1, T2, T3, T4, T5, T6, T7, F>>;
pub type Map7<T1, T2, T3, T4, T5, T6, T7, F> =
stream::WeakNode<Map7Data<T1, T2, T3, T4, T5, T6, T7, F>>;
impl<T1, T2, T3, T4, T5, T6, T7, F, Out> HasOutput for Map7Data<T1, T2, T3, T4, T5, T6, T7, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
Out: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
) -> Out,
{
type Output = Out;
}
impl<T1, T2, T3, T4, T5, T6, T7, F, Out> OwnedMap7<T1, T2, T3, T4, T5, T6, T7, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
Out: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
) -> 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<T1, T2, T3, T4, T5, T6, T7, F, Out> stream::EventConsumer<Output<T1>>
for OwnedMap7<T1, T2, T3, T4, T5, T6, T7, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
Out: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
) -> Out,
{
fn on_event(&self, stack: CallStack, value1: &Output<T1>) {
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<T1, T2, T3, T4, T5, T6, T7, F> stream::InputBehaviors
for Map7Data<T1, T2, T3, T4, T5, T6, T7, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
{
fn input_behaviors(&self) -> Vec<Link> {
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<T1, T2, T3, T4, T5, T6, T7, F> Debug for Map7Data<T1, T2, T3, T4, T5, T6, T7, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Map7Data")
}
}
// ============
// === Map8 ===
// ============
pub struct Map8Data<T1, T2, T3, T4, T5, T6, T7, T8, F> {
_src1: T1,
src2: watch::Ref<T2>,
src3: watch::Ref<T3>,
src4: watch::Ref<T4>,
src5: watch::Ref<T5>,
src6: watch::Ref<T6>,
src7: watch::Ref<T7>,
src8: watch::Ref<T8>,
function: F,
}
pub type OwnedMap8<T1, T2, T3, T4, T5, T6, T7, T8, F> =
stream::Node<Map8Data<T1, T2, T3, T4, T5, T6, T7, T8, F>>;
pub type Map8<T1, T2, T3, T4, T5, T6, T7, T8, F> =
stream::WeakNode<Map8Data<T1, T2, T3, T4, T5, T6, T7, T8, F>>;
impl<T1, T2, T3, T4, T5, T6, T7, T8, F, Out> HasOutput
for Map8Data<T1, T2, T3, T4, T5, T6, T7, T8, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
T8: EventOutput,
Out: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
) -> Out,
{
type Output = Out;
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, F, Out> OwnedMap8<T1, T2, T3, T4, T5, T6, T7, T8, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
T8: EventOutput,
Out: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
) -> 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<T1, T2, T3, T4, T5, T6, T7, T8, F, Out> stream::EventConsumer<Output<T1>>
for OwnedMap8<T1, T2, T3, T4, T5, T6, T7, T8, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
T8: EventOutput,
Out: Data,
F: 'static
+ Fn(
&Output<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
) -> Out,
{
fn on_event(&self, stack: CallStack, value1: &Output<T1>) {
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<T1, T2, T3, T4, T5, T6, T7, T8, F> stream::InputBehaviors
for Map8Data<T1, T2, T3, T4, T5, T6, T7, T8, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
T8: EventOutput,
{
fn input_behaviors(&self) -> Vec<Link> {
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<T1, T2, T3, T4, T5, T6, T7, T8, F> Debug for Map8Data<T1, T2, T3, T4, T5, T6, T7, T8, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Map8Data")
}
}
// ============
// === Map8 ===
// ============
pub struct Map9Data<T1, T2, T3, T4, T5, T6, T7, T8, T9, F> {
_src1: T1,
src2: watch::Ref<T2>,
src3: watch::Ref<T3>,
src4: watch::Ref<T4>,
src5: watch::Ref<T5>,
src6: watch::Ref<T6>,
src7: watch::Ref<T7>,
src8: watch::Ref<T8>,
src9: watch::Ref<T9>,
function: F,
}
pub type OwnedMap9<T1, T2, T3, T4, T5, T6, T7, T8, T9, F> =
stream::Node<Map9Data<T1, T2, T3, T4, T5, T6, T7, T8, T9, F>>;
pub type Map9<T1, T2, T3, T4, T5, T6, T7, T8, T9, F> =
stream::WeakNode<Map9Data<T1, T2, T3, T4, T5, T6, T7, T8, T9, F>>;
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9, F, Out> HasOutput
for Map9Data<T1, T2, T3, T4, T5, T6, T7, T8, T9, F>
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<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
&Output<T9>,
) -> Out,
{
type Output = Out;
}
impl<T1, T2, T3, T4, T5, T6, T7, T8, T9, F, Out> OwnedMap9<T1, T2, T3, T4, T5, T6, T7, T8, T9, F>
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<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
&Output<T9>,
) -> 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<T1, T2, T3, T4, T5, T6, T7, T8, T9, F, Out> stream::EventConsumer<Output<T1>>
for OwnedMap9<T1, T2, T3, T4, T5, T6, T7, T8, T9, F>
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<T1>,
&Output<T2>,
&Output<T3>,
&Output<T4>,
&Output<T5>,
&Output<T6>,
&Output<T7>,
&Output<T8>,
&Output<T9>,
) -> Out,
{
fn on_event(&self, stack: CallStack, value1: &Output<T1>) {
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<T1, T2, T3, T4, T5, T6, T7, T8, T9, F> stream::InputBehaviors
for Map9Data<T1, T2, T3, T4, T5, T6, T7, T8, T9, F>
where
T1: EventOutput,
T2: EventOutput,
T3: EventOutput,
T4: EventOutput,
T5: EventOutput,
T6: EventOutput,
T7: EventOutput,
T8: EventOutput,
T9: EventOutput,
{
fn input_behaviors(&self) -> Vec<Link> {
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<T1, T2, T3, T4, T5, T6, T7, T8, T9, F> Debug
for Map9Data<T1, T2, T3, T4, T5, T6, T7, T8, T9, F>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Map9Data")
}
}
// ================ // ================
// === AllWith2 === // === AllWith2 ===
// ================ // ================