From 115e9b4ffda411fd19aabcd88f781e1a43064cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Dani=C5=82o?= Date: Tue, 25 Apr 2023 18:06:11 +0200 Subject: [PATCH] Implementation of elements adding to List Editor and a lot of internal API (#6390) --- .../ensogl/component/list-editor/src/lib.rs | 446 ++++++++++++------ lib/rust/ensogl/component/slider/src/lib.rs | 425 +++++++---------- lib/rust/ensogl/component/slider/src/model.rs | 52 +- lib/rust/ensogl/core/src/gui/cursor.rs | 27 +- lib/rust/ensogl/examples/slider/src/lib.rs | 74 ++- lib/rust/frp/src/nodes.rs | 203 ++++++++ lib/rust/prelude/src/option.rs | 11 + lib/rust/types/src/dim.rs | 97 ++++ 8 files changed, 879 insertions(+), 456 deletions(-) diff --git a/lib/rust/ensogl/component/list-editor/src/lib.rs b/lib/rust/ensogl/component/list-editor/src/lib.rs index 63c9848d0a..f3cd7e4bf8 100644 --- a/lib/rust/ensogl/component/list-editor/src/lib.rs +++ b/lib/rust/ensogl/component/list-editor/src/lib.rs @@ -106,13 +106,10 @@ pub mod placeholder; // === Constants === // ================= -// FIXME: to be parametrized -const GAP: f32 = 20.0; - /// If set to true, animations will be running slow. This is useful for debugging purposes. pub const DEBUG_ANIMATION_SLOWDOWN: bool = false; -pub const DEBUG_PLACEHOLDERS_VIZ: bool = true; +pub const DEBUG_PLACEHOLDERS_VIZ: bool = false; /// Spring factor for animations. If [`DEBUG_ANIMATION_SLOWDOWN`] is set to true, this value will be /// used for animation simulators. @@ -275,16 +272,20 @@ ensogl_core::define_endpoints_2! { /// Remove the element at the given index. If the index is invalid, nothing will happen. remove(Index), + gap(f32), + secondary_axis_drag_threshold(f32), primary_axis_no_drag_threshold(f32), primary_axis_no_drag_threshold_decay_time(f32), thrashing_offset_ratio(f32), + enable_all_insertion_points(bool), + enable_last_insertion_point(bool), } Output { /// Fires whenever a new element was added to the list. on_item_added(Response<(Index, Weak)>), - // on_item_removed(Response<(Index, Weak)>), + on_item_removed(Response<(Index, Weak)>), /// 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 @@ -297,39 +298,35 @@ ensogl_core::define_endpoints_2! { #[derivative(Clone(bound = ""))] pub struct ListEditor { #[deref] - pub frp: Frp, - root: display::object::Instance, - model: SharedModel, - add_elem_icon: Rectangle, - remove_elem_icon: Rectangle, + pub frp: Frp, + root: display::object::Instance, + model: SharedModel, } #[derive(Debug)] pub struct Model { + cursor: Cursor, items: VecIndexedBy, ItemOrPlaceholderIndex>, root: display::object::Instance, layout: display::object::Instance, + gap: f32, } impl Model { /// Constructor. - pub fn new() -> Self { + pub fn new(cursor: &Cursor) -> Self { + let cursor = cursor.clone_ref(); let items = default(); let root = display::object::Instance::new(); let layout = display::object::Instance::new(); + let gap = default(); layout.use_auto_layout(); root.add_child(&layout); - Self { items, root, layout } + Self { cursor, items, root, layout, gap } } } -impl Default for Model { - fn default() -> Self { - Self::new() - } -} - -#[derive(Derivative, CloneRef, Debug, Default, Deref)] +#[derive(Derivative, CloneRef, Debug, Deref)] #[derivative(Clone(bound = ""))] pub struct SharedModel { rc: Rc>>, @@ -342,31 +339,13 @@ impl From> for SharedModel { } -impl ListEditor { +impl ListEditor { pub fn new(cursor: &Cursor) -> Self { let frp = Frp::new(); - let model = Model::new(); + let model = Model::new(cursor); let root = model.root.clone_ref(); - let add_elem_icon = Rectangle().build(|t| { - t.set_size(Vector2::new(20.0, 20.0)) - .set_color(color::Rgba::new(0.0, 1.0, 0.0, 1.0)) - .set_inset_border(2.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 0.0, 0.5)); - }); - - let remove_elem_icon = Rectangle().build(|t| { - t.set_size(Vector2::new(20.0, 20.0)) - .set_color(color::Rgba::new(1.0, 0.0, 0.0, 1.0)) - .set_inset_border(2.0) - .set_border_color(color::Rgba::new(0.0, 0.0, 0.0, 0.5)); - }); - add_elem_icon.set_y(-30.0); - root.add_child(&add_elem_icon); - remove_elem_icon.set_y(-30.0); - remove_elem_icon.set_x(30.0); - root.add_child(&remove_elem_icon); let model = model.into(); - Self { frp, root, model, add_elem_icon, remove_elem_icon }.init(cursor).init_frp_values() + Self { frp, root, model }.init(cursor).init_frp_values() } fn init(self, cursor: &Cursor) -> Self { @@ -375,33 +354,11 @@ impl ListEditor { let network = self.frp.network(); let model = &self.model; - let add_elem_icon_down = self.add_elem_icon.on_event::(); - let remove_elem_icon_down = self.remove_elem_icon.on_event::(); let on_down = model.borrow().layout.on_event_capturing::(); let on_up_source = scene.on_event::(); let on_move = scene.on_event::(); - frp::extend! { network - frp.private.output.request_new_item <+ add_elem_icon_down.map(f!([model] (_) { - let index = model.borrow_mut().items.len(); - Response::gui(index) - })); - - frp.remove <+ remove_elem_icon_down.constant(0); - - push_item_index <= frp.push.map(f!([model] (item) - item.upgrade().map(|t| model.borrow_mut().push((*t).clone())) - )); - on_item_pushed <- frp.push.map2(&push_item_index, |item, ix| Response::api((*ix, item.clone()))); - frp.private.output.on_item_added <+ on_item_pushed; - - insert_item_index <= frp.insert.map(f!([model] ((index, item)) - item.upgrade().map(|t| model.borrow_mut().insert(*index, (*t).clone())) - )); - on_item_inserted <- frp.insert.map2(&insert_item_index, |(_,item), ix| Response::api((*ix, item.clone()))); - frp.private.output.on_item_added <+ on_item_inserted; - // Do not pass events to children, as we don't know whether we are about to drag // them yet. @@ -414,51 +371,115 @@ impl ListEditor { is_down <- bool(&on_up, &on_down); on_move_down <- on_move.gate(&is_down); glob_pos_on_down <- on_down.map(|event| event.client_centered()); - glob_pos_on_move <- on_move_down.map(|event| event.client_centered()); - pos_on_down <- glob_pos_on_down.map(f!([model] (p) model.screen_to_object_space(*p))); - pos_on_move <- glob_pos_on_move.map(f!([model] (p) model.screen_to_object_space(*p))); - pos_diff_on_move <- pos_on_move.map2(&pos_on_down, |a, b| a - b); - pos_diff_on_down <- on_down.constant(Vector2::new(0.0, 0.0)); - pos_diff_on_up <- on_up_cleaning_phase.constant(Vector2::new(0.0, 0.0)); + glob_pos_on_move_down <- on_move_down.map(|event| event.client_centered()); + glob_pos_on_move <- on_move.map(|event| event.client_centered()); + pos_on_down <- glob_pos_on_down.map(f!((p) model.screen_to_object_space(*p))); + pos_on_move_down <- glob_pos_on_move_down.map(f!((p) model.screen_to_object_space(*p))); + pos_on_move <- glob_pos_on_move.map(f!((p) model.screen_to_object_space(*p))); + pos_diff_on_move <- pos_on_move_down.map2(&pos_on_down, |a, b| a - b); + pos_diff_on_down <- on_down.constant(Vector2(0.0, 0.0)); + pos_diff_on_up <- on_up_cleaning_phase.constant(Vector2(0.0, 0.0)); pos_diff <- any3(&pos_diff_on_move, &pos_diff_on_down, &pos_diff_on_up); + + eval frp.gap((t) model.borrow_mut().set_gap(*t)); } - let (_is_dragging, drag_diff) = self.init_drag(&on_up, &on_down, &pos_diff); - let is_trashing = self.init_trashing(cursor, &on_up, &drag_diff); + self.init_add_and_remove(); + let (is_dragging, drag_diff) = self.init_dragging(&on_up, &on_down, &target, &pos_diff); + let (is_trashing, trash_pointer_style) = self.init_trashing(&on_up, &drag_diff); + self.init_dropping(&on_up, &pos_on_move_down, &is_trashing); + let insert_pointer_style = self.init_insertion_points(&on_up, &pos_on_move, &is_dragging); frp::extend! { network - status <- bool(&on_up, &drag_diff).on_change(); - start <- status.on_true(); - target_on_start <- target.sample(&start); - - eval frp.remove((index) model.borrow_mut().trash_item_at(*index)); - - // Re-parent the dragged element. - eval target_on_start([model, cursor] (t) model.borrow_mut().start_item_drag(&cursor, t)); - // frp.private.output.on_item_removed <+ on_item_inserted; - - pos_non_trash <- pos_on_move.gate_not(&is_trashing); - insert_index <- pos_non_trash.map(f!((pos) model.borrow().insert_index(pos.x))).on_change(); - insert_index_on_drop <- insert_index.sample(&on_up).gate_not(&is_trashing); - - eval insert_index ([model, cursor] (i) model.borrow_mut().add_insertion_point(&cursor, *i)); - - eval insert_index_on_drop ([cursor, model] (index) - model.borrow_mut().place_dragged_item(&cursor, *index) - ); + cursor.frp.set_style <+ all [insert_pointer_style, trash_pointer_style].fold(); } self } + fn init_insertion_points( + &self, + on_up: &frp::Stream>, + pos_on_move: &frp::Stream, + is_dragging: &frp::Stream, + ) -> frp::Stream { + let on_up = on_up.clone_ref(); + let pos_on_move = pos_on_move.clone_ref(); + let is_dragging = is_dragging.clone_ref(); + + let frp = &self.frp; + let network = self.frp.network(); + let model = &self.model; + let model_borrowed = model.borrow(); + + frp::extend! { network + gaps <- model_borrowed.layout.on_resized.map(f_!(model.gaps())); + opt_index <- all_with7( + &frp.gap, + &gaps, + &pos_on_move, + &model.borrow().layout.on_resized, + &is_dragging, + &frp.enable_all_insertion_points, + &frp.enable_last_insertion_point, + f!([model] (gap, gaps, pos, size, is_dragging, enable_all, enable_last) { + let is_close_x = pos.x > -gap && pos.x < size.x + gap; + let is_close_y = pos.y > -gap && pos.y < size.y + gap; + let is_close = is_close_x && is_close_y; + let opt_gap = gaps.find(pos.x); + opt_gap.and_then(|gap| { + let last_gap = *gap == gaps.len() - 1; + let enabled = is_close && !is_dragging; + let enabled = enabled && (*enable_all || (*enable_last && last_gap)); + enabled.and_option_from(|| model.item_or_placeholder_index_to_index(gap)) + }) + }) + ); + index <= opt_index; + enabled <- opt_index.is_some(); + pointer_style <- opt_index.map(|t| t.if_some_or_default(cursor::Style::plus)); + on_up_in_gap <- on_up.gate(&enabled); + insert_in_gap <- index.sample(&on_up_in_gap); + frp.private.output.request_new_item <+ insert_in_gap.map(|t| Response::gui(*t)); + } + pointer_style + } + + /// Implementation of adding and removing items logic. + fn init_add_and_remove(&self) { + let model = &self.model; + let frp = &self.frp; + let network = self.frp.network(); + + frp::extend! { network + push_ix <= frp.push.map(f!((item) model.push_weak(item))); + on_pushed <- frp.push.map2(&push_ix, |t, ix| Response::api((*ix, t.clone()))); + frp.private.output.on_item_added <+ on_pushed; + + insert_ix <= frp.insert.map(f!(((index, item)) model.insert_weak(*index, item))); + on_inserted <- frp.insert.map2(&insert_ix, |t, ix| Response::api((*ix, t.1.clone()))); + frp.private.output.on_item_added <+ on_inserted; + + let on_item_removed = &frp.private.output.on_item_removed; + eval frp.remove([model, on_item_removed] (index) { + if let Some(item) = model.borrow_mut().trash_item_at(*index) { + on_item_removed.emit(Response::api((*index, Rc::new(item).downgrade()))); + } + }); + } + } + /// Implementation of item dragging logic. See docs of this crate to learn more. - fn init_drag( + fn init_dragging( &self, on_up: &frp::Stream>, on_down: &frp::Stream>, + target: &frp::Stream, pos_diff: &frp::Stream, ) -> (frp::Stream, frp::Stream) { + let model = &self.model; let on_up = on_up.clone_ref(); let on_down = on_down.clone_ref(); + let target = target.clone_ref(); let pos_diff = pos_diff.clone_ref(); let frp = &self.frp; let network = self.frp.network(); @@ -478,17 +499,26 @@ impl ListEditor { init_drag_not_disabled <- init_drag.gate_not(&drag_disabled); is_dragging <- bool(&on_up, &init_drag_not_disabled).on_change(); drag_diff <- pos_diff.gate(&is_dragging); + + status <- bool(&on_up, &drag_diff).on_change(); + start <- status.on_true(); + target_on_start <- target.sample(&start); + let on_item_removed = &frp.private.output.on_item_removed; + eval target_on_start([model, on_item_removed] (t) { + if let Some((index, item)) = model.borrow_mut().start_item_drag(t) { + on_item_removed.emit(Response::gui((index, Rc::new(item).downgrade()))); + } + }); } - (is_dragging, drag_diff) + (status, drag_diff) } /// Implementation of item trashing logic. See docs of this crate to learn more. fn init_trashing( &self, - cursor: &Cursor, on_up: &frp::Stream>, drag_diff: &frp::Stream, - ) -> frp::Stream { + ) -> (frp::Stream, frp::Stream) { let on_up = on_up.clone_ref(); let drag_diff = drag_diff.clone_ref(); let model = &self.model; @@ -502,21 +532,61 @@ impl ListEditor { status <- drag_diff.map2(&required_offset, |t, m| t.y.abs() >= *m).on_change(); status_on_up <- on_up.constant(false); status_cleaning_phase <- any(&status, &status_on_up).on_change(); - cursor.frp.set_style <+ status_cleaning_phase.default_or(cursor::Style::trash()); + cursor_style <- status_cleaning_phase.default_or(cursor::Style::trash()); on <- status.on_true(); perform <- on_up.gate(&status); eval_ on (model.collapse_all_placeholders()); - eval_ perform ([model, cursor] model.borrow_mut().trash_dragged_item(&cursor)); + eval_ perform (model.borrow_mut().trash_dragged_item()); + } + (status, cursor_style) + } + + /// Implementation of dropping items logic, including showing empty placeholders when the item + /// is dragged over a place where it could be dropped. + fn init_dropping( + &self, + on_up: &frp::Stream>, + pos_on_move: &frp::Stream, + is_trashing: &frp::Stream, + ) { + let pos_on_move = pos_on_move.clone_ref(); + let is_trashing = is_trashing.clone_ref(); + + let model = &self.model; + let frp = &self.frp; + let network = self.frp.network(); + let model_borrowed = model.borrow(); + + frp::extend! { network + center_points <- model_borrowed.layout.on_resized.map(f_!(model.center_points())); + insert_index <- pos_on_move.map2(¢er_points, f!((p, c) model.insert_index(p.x, c))); + insert_index <- insert_index.on_change(); + insert_index_on_drop <- insert_index.sample(on_up).gate_not(&is_trashing); + insert_index_not_trashing <- insert_index.gate_not(&is_trashing); + + on_stop_trashing <- is_trashing.on_false(); + insert_index_on_stop_trashing <- insert_index.sample(&on_stop_trashing); + update_insert_index <- any(&insert_index_not_trashing, &insert_index_on_stop_trashing); + eval update_insert_index ((i) model.borrow_mut().add_insertion_point(*i)); + + let on_item_added = &frp.private.output.on_item_added; + eval insert_index_on_drop ([model, on_item_added] (index) + if let Some((index, item)) = model.borrow_mut().place_dragged_item(*index) { + on_item_added.emit(Response::gui((index, Rc::new(item).downgrade()))); + } + ); } - status } /// Initializes default FRP values. See docs of this crate to learn more. fn init_frp_values(self) -> Self { + self.frp.gap(10.0); self.frp.secondary_axis_drag_threshold(4.0); self.frp.primary_axis_no_drag_threshold(4.0); self.frp.primary_axis_no_drag_threshold_decay_time(1000.0); self.frp.thrashing_offset_ratio(1.0); + self.frp.enable_all_insertion_points(true); + self.frp.enable_last_insertion_point(true); self } @@ -529,7 +599,7 @@ impl ListEditor { } } -impl SharedModel { +impl SharedModel { fn screen_to_object_space(&self, screen_pos: Vector2) -> Vector2 { self.borrow().screen_to_object_space(screen_pos) } @@ -537,9 +607,52 @@ impl SharedModel { fn collapse_all_placeholders(&self) { self.borrow_mut().collapse_all_placeholders() } + + fn push(&self, item: T) -> Index { + self.borrow_mut().push(item) + } + + fn push_weak(&self, item: &Weak) -> Option { + item.upgrade().map(|item| self.push((*item).clone_ref())) + } + + fn insert(&self, index: Index, item: T) -> Index { + self.borrow_mut().insert(index, item) + } + + fn insert_weak(&self, index: Index, item: &Weak) -> Option { + item.upgrade().map(|item| self.insert(index, (*item).clone_ref())) + } + + fn insert_index(&self, x: f32, center_points: &[f32]) -> ItemOrPlaceholderIndex { + self.borrow().insert_index(x, center_points) + } + + fn gaps(&self) -> Gaps { + self.borrow().gaps() + } + + fn center_points(&self) -> Vec { + self.borrow().center_points() + } + + fn item_or_placeholder_index_to_index(&self, ix: ItemOrPlaceholderIndex) -> Option { + self.borrow().item_or_placeholder_index_to_index(ix) + } } -impl Model { +#[derive(Clone, Debug, Default, Deref)] +pub struct Gaps { + gaps: Vec>, +} + +impl Gaps { + pub fn find(&self, x: f32) -> Option { + self.gaps.iter().position(|gap| gap.contains(&x)).map(|t| t.into()) + } +} + +impl Model { // FIXME: refactor and generalize fn screen_to_object_space(&self, screen_pos: Vector2) -> Vector2 { let scene = scene(); @@ -561,18 +674,41 @@ impl Model { self.items.iter().filter(|t| t.is_item()).count() } + fn set_gap(&mut self, gap: f32) { + self.gap = gap; + self.recompute_margins(); + } + /// Find an element by the provided display object reference. - fn item_index_of(&mut self, obj: &display::object::Instance) -> Option { - self.items.iter().enumerate().find(|t| t.1.cmp_item_display_object(obj)).map(|t| t.0.into()) + fn item_index_of( + &mut self, + obj: &display::object::Instance, + ) -> Option<(Index, ItemOrPlaceholderIndex)> { + self.items + .iter() + .enumerate() + .map(|(i, t)| (ItemOrPlaceholderIndex::from(i), t)) + .filter(|(_, t)| t.is_item()) + .enumerate() + .find(|(_, (_, t))| t.cmp_item_display_object(obj)) + .map(|(i1, (i2, _))| (i1, i2)) } /// Convert the item index to item or placeholder index. - fn index_to_item_or_placeholder_index(&mut self, ix: Index) -> Option { + fn index_to_item_or_placeholder_index(&self, ix: Index) -> Option { self.items.iter().enumerate().filter(|(_, item)| item.is_item()).nth(ix).map(|t| t.0.into()) } - fn item_or_placeholder_index_to_index(&mut self, ix: ItemOrPlaceholderIndex) -> Option { - self.items.iter().enumerate().filter(|(_, item)| item.is_item()).position(|t| t.0 == *ix) + fn item_or_placeholder_index_to_index(&self, ix: ItemOrPlaceholderIndex) -> Option { + if *ix == self.items.len() { + Some(self.len()) + } else { + self.items + .iter() + .enumerate() + .filter(|(_, item)| item.is_item()) + .position(|t| t.0 == *ix) + } } fn push(&mut self, item: T) -> Index { @@ -616,7 +752,7 @@ impl Model { ItemOrPlaceholder::Placeholder(Placeholder::Weak(_)) => {} ItemOrPlaceholder::Placeholder(Placeholder::Strong(_)) => first_elem = false, ItemOrPlaceholder::Item(t) => { - t.set_margin_left(if first_elem { 0.0 } else { GAP }); + t.set_margin_left(if first_elem { 0.0 } else { self.gap }); first_elem = false; } } @@ -631,7 +767,7 @@ impl Model { /// Get the margin at the given insertion point. If the insertion point is before the first /// item, the margin will be 0. fn margin_at(&self, index: ItemOrPlaceholderIndex) -> f32 { - self.first_item_index().map_or(0.0, |i| if index <= i { 0.0 } else { GAP }) + self.first_item_index().map_or(0.0, |i| if index <= i { 0.0 } else { self.gap }) } /// Retain only items and placeholders that did not collapse yet (both strong and weak ones). @@ -704,19 +840,14 @@ impl Model { /// be reused and scaled to cover the size of the dragged element. /// /// See docs of [`Self::start_item_drag_at`] for more information. - fn start_item_drag( - &mut self, - cursor: &Cursor, - target: &display::object::Instance, - ) -> Option { + fn start_item_drag(&mut self, target: &display::object::Instance) -> Option<(Index, T)> { let index = self.item_index_of(target); - if let Some(index) = index { - self.start_item_drag_at(cursor, index); + if let Some((index, index_or_placeholder_index)) = index { + self.start_item_drag_at(index_or_placeholder_index).map(|item| (index, item)) } else { - warn!("Requested to drag a non-existent item.") + warn!("Requested to drag a non-existent item."); + None } - // Fixme: this could break easily during refactoring. - index.and_then(|t| self.item_or_placeholder_index_to_index(t)) } /// Remove the selected item from the item list and mark it as an element being dragged. In the @@ -758,10 +889,11 @@ impl Model { /// │ A │ ┆ ┆ │ X │ ┆ ┆ │ B │ ------> │ A │ ┆ ╰─────╯ ┆ │ B │ /// ╰─────╯ ╰╌╌╌╌╯ ╰─────╯ ╰╌╌╌╌╯ ╰─────╯ ╰─────╯ ╰╌╌╌╌╌╌╌╌╌╌╌╌◀╌╯ ╰─────╯ /// ``` - fn start_item_drag_at(&mut self, cursor: &Cursor, index: ItemOrPlaceholderIndex) { - if let Some(item) = self.replace_item_with_placeholder(index) { - cursor.start_drag(item); - } + fn start_item_drag_at(&mut self, index: ItemOrPlaceholderIndex) -> Option { + self.replace_item_with_placeholder(index).map(|item| { + self.cursor.start_drag(item.clone_ref()); + item + }) } fn replace_item_with_placeholder(&mut self, index: ItemOrPlaceholderIndex) -> Option { @@ -788,8 +920,10 @@ impl Model { /// Prepare place for the dragged item by creating or reusing a placeholder and growing it to /// the dragged object size. - fn add_insertion_point(&mut self, cursor: &Cursor, index: ItemOrPlaceholderIndex) { - if let Some(item) = cursor.with_dragged_item_if_is::(|t| t.display_object().clone()) { + fn add_insertion_point(&mut self, index: ItemOrPlaceholderIndex) { + if let Some(item) = + self.cursor.with_dragged_item_if_is::(|t| t.display_object().clone()) + { self.collapse_all_placeholders_no_margin_update(); let item_size = item.computed_size().x + self.margin_at(index); let placeholder = self.get_merged_placeholder_at(index).unwrap_or_else(|| { @@ -807,24 +941,27 @@ impl Model { /// Place the currently dragged element in the given index. The item will be enclosed in the /// [`Item`] object, will handles its animation. See the documentation of /// [`ItemOrPlaceholder`] to learn more. - fn place_dragged_item(&mut self, cursor: &Cursor, index: ItemOrPlaceholderIndex) { - if let Some(element) = cursor.stop_drag_if_is::() { + fn place_dragged_item(&mut self, index: ItemOrPlaceholderIndex) -> Option<(Index, T)> { + if let Some(item) = self.cursor.stop_drag_if_is::() { self.collapse_all_placeholders_no_margin_update(); if let Some((index, placeholder)) = self.get_indexed_merged_placeholder_at(index) { placeholder.set_target_size(placeholder.computed_size().x); - element.update_xy(|t| t - placeholder.global_position().xy()); - self.items[index] = Item::new_from_placeholder(element, placeholder).into(); + item.update_xy(|t| t - placeholder.global_position().xy()); + self.items[index] = + Item::new_from_placeholder(item.clone_ref(), placeholder).into(); } else { - // This branch should never be reached, as when dragging an element we always create + // This branch should never be reached, as when dragging an item we always create // a placeholder for it (see the [`Self::add_insertion_point`] function). However, // in case something breaks, we want it to still provide the user with the correct // outcome. - self.items.insert(index, Item::new(element).into()); + self.items.insert(index, Item::new(item.clone_ref()).into()); warn!("An element was inserted without a placeholder. This should not happen."); } self.reposition_items(); + self.item_or_placeholder_index_to_index(index).map(|index| (index, item)) } 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 } } @@ -833,21 +970,22 @@ impl Model { self.reposition_items(); } - pub fn trash_dragged_item(&mut self, cursor: &Cursor) { + pub fn trash_dragged_item(&mut self) { warn!("Trash dragged item."); - if let Some(item) = cursor.stop_drag_if_is::() { + if let Some(item) = self.cursor.stop_drag_if_is::() { self.trash_item(item) } } - pub fn trash_item_at(&mut self, index: Index) { - if let Some(item_index) = self.index_to_item_or_placeholder_index(index) { - if let Some(item) = self.replace_item_with_placeholder(item_index) { - self.collapse_all_placeholders_no_margin_update(); - self.trash_item(item); - } + pub fn trash_item_at(&mut self, index: Index) -> Option { + if let Some(item_index) = self.index_to_item_or_placeholder_index(index) + && let Some(item) = self.replace_item_with_placeholder(item_index) { + self.collapse_all_placeholders_no_margin_update(); + self.trash_item(item.clone_ref()); + Some(item) } else { warn!("Wrong index."); + None } } @@ -874,9 +1012,27 @@ impl Model { centers } + fn gaps(&self) -> Gaps { + let mut gaps = Vec::new(); + gaps.push(f32::NEG_INFINITY..=0.0); + let mut fist_gap = true; + let mut current = 0.0; + for item in &self.items { + let start = current; + current += item.margin_left(); + if !fist_gap { + gaps.push(start..=current); + } + fist_gap = false; + current += item.target_size2(); + } + gaps.push(current..=f32::INFINITY); + Gaps { gaps } + } + /// The insertion point of the given vertical offset. - fn insert_index(&self, x: f32) -> ItemOrPlaceholderIndex { - self.center_points().iter().position(|t| x < *t).unwrap_or(self.items.len()).into() + fn insert_index(&self, x: f32, center_points: &[f32]) -> ItemOrPlaceholderIndex { + center_points.iter().position(|t| x < *t).unwrap_or(self.items.len()).into() } } @@ -967,20 +1123,20 @@ pub fn main() { let shape1 = Circle().build(|t| { - t.set_size(Vector2::new(60.0, 100.0)) + 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::new(120.0, 100.0)) + 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::new(240.0, 100.0)) + 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)); @@ -997,15 +1153,15 @@ pub fn main() { }); new_item <- vector_editor.request_new_item.map(|_| { let shape = RoundedRectangle(10.0).build(|t| { - t.set_size(Vector2::new(100.0, 100.0)) + 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.push <+ vector_editor.request_new_item.map2(&new_item, |_, item| - item.downgrade() + vector_editor.insert <+ vector_editor.request_new_item.map2(&new_item, |index, item| + (**index, item.downgrade()) ); } @@ -1014,7 +1170,7 @@ pub fn main() { vector_editor.push(shape3); let root = display::object::Instance::new(); - root.set_size(Vector2::new(300.0, 100.0)); + root.set_size(Vector2(300.0, 100.0)); root.add_child(&vector_editor); world.add_child(&root); diff --git a/lib/rust/ensogl/component/slider/src/lib.rs b/lib/rust/ensogl/component/slider/src/lib.rs index 4e28a21541..f7b689677c 100644 --- a/lib/rust/ensogl/component/slider/src/lib.rs +++ b/lib/rust/ensogl/component/slider/src/lib.rs @@ -27,10 +27,11 @@ use ensogl_core::application; use ensogl_core::application::shortcut; use ensogl_core::application::tooltip; use ensogl_core::application::Application; +use ensogl_core::control::io::mouse; use ensogl_core::data::color; use ensogl_core::display; use ensogl_core::Animation; -use ensogl_text::formatting; +use ensogl_text::formatting::Weight; // ============== @@ -49,7 +50,7 @@ pub mod model; /// much the value is changed per pixel dragged and how many digits are displayed after the decimal. const PRECISION_DEFAULT: f32 = 1.0; /// Default upper limit of the slider value. -const MAX_VALUE_DEFAULT: f32 = 1.0; +const MAX_VALUE_DEFAULT: f32 = 100.0; /// Default for the maximum number of digits after the decimal point that is displayed. const MAX_DISP_DECIMAL_PLACES_DEFAULT: usize = 8; /// Margin above/below the component within which vertical mouse movement will not affect slider @@ -103,15 +104,15 @@ pub enum LabelPosition { // === Slider orientation === // ========================== -/// The orientation of the slider component. -#[derive(Clone, Copy, Debug, Default)] -pub enum SliderOrientation { - #[default] - /// The slider value is changed by dragging the slider horizontally. - Horizontal, - /// The slider value is changed by dragging the slider vertically. - Vertical, -} +// /// The orientation of the slider component. +// #[derive(Clone, Copy, Debug, Default)] +// pub enum Axis2 { +// #[default] +// /// The slider value is changed by dragging the slider horizontally. +// Horizontal, +// /// The slider value is changed by dragging the slider vertically. +// Vertical, +// } @@ -121,15 +122,15 @@ pub enum SliderOrientation { /// The type of element that indicates the slider's value along its length. #[derive(Clone, Copy, Debug, Default)] -pub enum ValueIndicator { +pub enum Kind { #[default] /// A track is a bar that fills the slider as the value increases. The track is empty when the /// slider's value is at the lower limit and filled when the value is at the upper limit. - Track, + 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. - Thumb, + Scrollbar(f32), } @@ -160,15 +161,19 @@ pub enum SliderLimit { /// Adaptive upper limit adjustment. fn adapt_upper_limit( - &(value, min, max, max_ext, upper_limit): &(f32, f32, f32, f32, SliderLimit), + value: f32, + min: f32, + max: f32, + max_ext: f32, + upper_limit: SliderLimit, ) -> f32 { if upper_limit == SliderLimit::Adaptive && value > max { let range = max_ext - min; let extend = value > max_ext; let shrink = value < min + range * ADAPTIVE_LIMIT_SHRINK_THRESHOLD; let max_ext = match (extend, shrink) { - (true, _) => adapt_upper_limit(&(value, min, max, min + range * 2.0, upper_limit)), - (_, true) => adapt_upper_limit(&(value, min, max, min + range * 0.5, upper_limit)), + (true, _) => adapt_upper_limit(value, min, max, min + range * 2.0, upper_limit), + (_, true) => adapt_upper_limit(value, min, max, min + range * 0.5, upper_limit), _ => max_ext, }; max_ext.max(max) // Do no set extended limit below original `max`. @@ -179,15 +184,19 @@ fn adapt_upper_limit( /// Adaptive lower limit adjustment. fn adapt_lower_limit( - &(value, min, max, min_ext, lower_limit): &(f32, f32, f32, f32, SliderLimit), + value: f32, + min: f32, + max: f32, + min_ext: f32, + lower_limit: SliderLimit, ) -> f32 { if lower_limit == SliderLimit::Adaptive && value < min { let range = max - min_ext; let extend = value < min_ext; let shrink = value > max - range * ADAPTIVE_LIMIT_SHRINK_THRESHOLD; let min_ext = match (extend, shrink) { - (true, _) => adapt_lower_limit(&(value, min, max, max - range * 2.0, lower_limit)), - (_, true) => adapt_lower_limit(&(value, min, max, max - range * 0.5, lower_limit)), + (true, _) => adapt_lower_limit(value, min, max, max - range * 2.0, lower_limit), + (_, true) => adapt_lower_limit(value, min, max, max - range * 0.5, lower_limit), _ => min_ext, }; min_ext.min(min) // Do no set extended limit above original `min`. @@ -216,12 +225,12 @@ fn value_limit_clamp( ensogl_core::define_endpoints_2! { Input { - /// Set the width of the slider component. - set_width(f32), - /// Set the height of the slider component. - set_height(f32), + // /// Set the 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. - set_value_indicator(ValueIndicator), + kind(Kind), /// Set the color of the slider's value indicator. set_value_indicator_color(color::Lcha), /// Set the color of the slider's background. @@ -239,7 +248,7 @@ ensogl_core::define_endpoints_2! { /// Set the color of the text displaying the current value. set_value_text_color(color::Lcha), /// Set whether the slider's value text is hidden. - set_value_text_hidden(bool), + show_value(bool), /// Set the default precision at which the slider operates. The slider's precision /// determines by what increment the value will be changed on mouse movement. It also /// affects the number of digits after the decimal point displayed. @@ -267,7 +276,7 @@ ensogl_core::define_endpoints_2! { /// Set the position of the slider's label. set_label_position(LabelPosition), /// Set the orientation of the slider component. - set_orientation(SliderOrientation), + orientation(Axis2), /// Set a tooltip that pops up when the mose hovers over the component. set_tooltip(ImString), /// Set the delay of the tooltip showing after the mouse hovers over the component. @@ -317,8 +326,8 @@ ensogl_core::define_endpoints_2! { disabled(bool), /// Indicates whether the slider's value is being edited currently. editing(bool), - /// The orientation of the slider, either horizontal or vertical. - orientation(SliderOrientation), + // /// The orientation of the slider, either horizontal or vertical. + // orientation(Axis2), } } @@ -357,13 +366,14 @@ impl Slider { fn init(self) -> Self { self.init_value_update(); - self.init_value_editing(); self.init_limit_handling(); self.init_value_display(); + + self.init_value_editing(); self.init_precision_popup(); self.init_information_tooltip(); self.init_component_layout(); - self.init_component_colors(); + // self.init_component_colors(); self.init_slider_defaults(); self } @@ -371,182 +381,109 @@ impl Slider { /// Initialize the slider value update FRP network. fn init_value_update(&self) { let network = self.frp.network(); + let frp = &self.frp; let input = &self.frp.input; let output = &self.frp.private.output; let model = &self.model; let scene = &self.app.display.default_scene; let mouse = &scene.mouse.frp_deprecated; let keyboard = &scene.keyboard.frp; - let component_events = &model.background.events_deprecated; + + let ptr_down_any = model.background.on_event::(); + let ptr_up_any = scene.on_event::(); + let ptr_out = model.background.on_event::(); + let ptr_over = model.background.on_event::(); + + let obj = model.display_object(); frp::extend! { network - - // === User input === - - component_click <- component_events.mouse_down_primary - .gate_not(&input.set_slider_disabled); - component_click <- component_click.gate_not(&output.editing); - slider_disabled_is_true <- input.set_slider_disabled.on_true(); - slider_editing_is_true <- output.editing.on_true(); - component_release <- any3( - &component_events.mouse_release_primary, - &slider_disabled_is_true, - &slider_editing_is_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(); + pos <- mouse.position.map( + f!([scene, model] (p) scene.screen_to_object_space(&model.background, *p)) ); - component_drag <- bool(&component_release, &component_click); - component_drag <- component_drag.gate_not(&input.set_slider_disabled); - component_drag <- component_drag.gate_not(&keyboard.is_control_down); - component_ctrl_click <- component_click.gate(&keyboard.is_control_down); - drag_start_pos <- mouse.position.sample(&component_click); - drag_end_pos <- mouse.position.gate(&component_drag); - drag_end_pos <- any2(&drag_end_pos, &drag_start_pos); - drag_delta <- all2(&drag_end_pos, &drag_start_pos).map(|(end, start)| end - start); - drag_delta_primary <- all2(&drag_delta, &input.set_orientation); - drag_delta_primary <- drag_delta_primary.map( |(delta, orientation)| - match orientation { - SliderOrientation::Horizontal => delta.x, - SliderOrientation::Vertical => delta.y, - } - ).on_change(); - mouse_position_click <- mouse.position.sample(&component_click); - mouse_position_drag <- mouse.position.gate(&component_drag); - mouse_position_click_or_drag <- any2(&mouse_position_click, &mouse_position_drag); - mouse_local <- mouse_position_click_or_drag.map( - f!([scene, model] (pos) scene.screen_to_object_space(&model.background, *pos)) - ); - mouse_local_secondary <- all2(&mouse_local, &input.set_orientation); - mouse_local_secondary <- mouse_local_secondary.map( |(offset, orientation)| - match orientation { - SliderOrientation::Horizontal => offset.y, - SliderOrientation::Vertical => offset.x, - } - ); - output.hovered <+ bool(&component_events.mouse_out, &component_events.mouse_over); - output.dragged <+ component_drag; + value_on_ptr_down <- output.value.sample(&ptr_down); + ptr_down <- ptr_down.gate_not(&frp.set_slider_disabled); + ptr_down <- ptr_down.gate_not(&output.editing); + on_disabled <- input.set_slider_disabled.on_true(); + on_editing <- output.editing.on_true(); + on_drag_start <- ptr_down.gate_not(&keyboard.is_control_down); + on_drag_stop <- any3(&ptr_up, &on_disabled, &on_editing); + dragging <- bool(&on_drag_stop, &on_drag_start); + drag_start <- pos.sample(&on_drag_start); + drag_end <- pos.gate(&dragging).any2(&drag_start); + drag_delta <- all2(&drag_end, &drag_start).map(|(end, start)| end - start); + drag_delta1 <- all_with(&drag_delta, &frp.orientation, |t, d| t.get_dim(d)).on_change(); + orientation_orth <- frp.orientation.map(|o| o.orthogonal()); + prec_delta <- all_with(&drag_end, &orientation_orth, |t, d| t.get_dim(d)).on_change(); - // === Get slider value on drag start === - - value_reset <- input.set_default_value.sample(&component_ctrl_click); - value_on_click <- output.value.sample(&component_click); - value_on_click <- any2(&value_reset, &value_on_click); + output.hovered <+ bool(&ptr_out, &ptr_over); + output.dragged <+ dragging; // === Precision calculation === - slider_length <- all3(&input.set_orientation, &input.set_width, &input.set_height); - slider_length <- slider_length.map( |(orientation, width, height)| - match orientation { - SliderOrientation::Horizontal => *width, - SliderOrientation::Vertical => *height, - } - ); - slider_length <- all3( - &slider_length, - &input.set_value_indicator, - &input.set_thumb_size - ); - slider_length <- slider_length.map(|(length, indicator, thumb_size)| - match indicator { - ValueIndicator::Thumb => length * (1.0 - thumb_size), - ValueIndicator::Track => *length, - } - ); - min_value_on_click <- output.min_value.sample(&component_click); - min_value_on_click <- any2(&min_value_on_click, &input.set_min_value); - max_value_on_click <- output.max_value.sample(&component_click); - max_value_on_click <- any2(&max_value_on_click, &input.set_max_value); - slider_range <- all2(&min_value_on_click, &max_value_on_click); - slider_range <- slider_range.map(|(min, max)| max - min); - prec_at_mouse_speed <- all2(&slider_length, &slider_range).map(|(l, r)| r / l); + 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)); - output.precision <+ prec_at_mouse_speed.sample(&component_click); - precision_adjustment_margin <- all4( - &input.set_width, - &input.set_height, - &input.set_precision_adjustment_margin, - &input.set_orientation, - ); - precision_adjustment_margin <- precision_adjustment_margin.map( - |(width, height, margin, orientation)| match orientation { - SliderOrientation::Horizontal => height / 2.0 + margin, - SliderOrientation::Vertical => width / 2.0 + margin, + 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, } ); - precision_offset_steps <- all3( - &mouse_local_secondary, - &precision_adjustment_margin, - &input.set_precision_adjustment_step_size, - ); - precision_offset_steps <- precision_offset_steps.map( - |(offset, margin, step_size)| { - let sign = offset.signum(); - // Calculate mouse y-position offset beyond margin. - let offset = offset.abs() - margin; - if offset < 0.0 { return None } // No adjustment if offset is within margin. - // Calculate number of steps and direction of the precision adjustment. - let steps = (offset / step_size).ceil() * sign; - match steps { - // Step 0 is over the component, which returns early. Make step 0 be the - // first adjustment step above the component (precision = 1.0). - steps if steps > 0.0 => Some(steps - 1.0), - steps => Some(steps), - } + + 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, + &frp.set_precision_adjustment_margin, + &prec_delta, + &frp.set_precision_adjustment_step_size, + &frp.set_max_precision_adjustment_steps, + |width, margin, prec_delta, step_size, max_steps| { + let prec_margin = width / 2.0 + margin; + let sign = prec_delta.signum() as i32; + let offset = prec_delta.abs() - prec_margin; + let level = min(*max_steps as i32, (offset / step_size).ceil() as i32) * sign; + (level != 0).as_some_from(|| { + let exp = if level > 0 { level - 1 } else { level }; + let precision = 10.0_f32.powf(exp as f32); + precision + }) } ).on_change(); - precision_offset_steps <- all2( - &precision_offset_steps, - &input.set_max_precision_adjustment_steps, - ); - precision_offset_steps <- precision_offset_steps.map(|(step, max_step)| - step.map(|step| step.clamp(- (*max_step as f32), *max_step as f32)) - ); - precision <- all4( - &prec_at_mouse_speed, - &input.set_default_precision, - &precision_offset_steps, - &input.set_precision_adjustment_disabled, - ); - precision <- precision.map( - |(mouse_prec, step_prec, offset, disabled)| match (offset, disabled) { - // Adjust the precision by the number of offset steps. - (Some(offset), false) => - *step_prec * (PRECISION_ADJUSTMENT_STEP_BASE).pow(*offset), - // Set the precision for 1:1 track movement to mouse movement. - _ => *mouse_prec, - } - ); + precision <- all_with(&non_native_precision, &native_precision, |t,s| t.unwrap_or(*s)); + output.precision <+ precision; // === Value calculation === - update_value <- bool(&component_release, &value_on_click); - value <- all3(&value_on_click, &precision, &drag_delta_primary); - value <- value.gate(&update_value); - value <- value.map(|(value, precision, delta)| value + delta * precision); - value <- any2(&input.set_value, &value); - // Snap the slider's value to the nearest precision increment. - value <- all2(&value, &precision); - value <- value.map(|(value, precision)| (value / precision).round() * precision); + value <- drag_delta1.map3(&value_on_ptr_down, &precision, + |delta, value, precision| value + delta * precision); + value <- any2(&frp.set_value, &value); value <- all5( &value, - &input.set_min_value, - &input.set_max_value, - &input.set_lower_limit_type, - &input.set_upper_limit_type, + &frp.set_min_value, + &frp.set_max_value, + &frp.set_lower_limit_type, + &frp.set_upper_limit_type, ).map(value_limit_clamp); output.value <+ value; - output.precision <+ precision; - model.value_animation.target <+ value; - small_value_step <- all2(&precision, &prec_at_mouse_speed); - small_value_step <- small_value_step.map(|(prec, threshold)| prec <= threshold); - value_adjust <- drag_delta_primary.map(|x| *x != 0.0); - prec_adjust <- precision.on_change(); - prec_adjust <- bool(&value_adjust, &prec_adjust); - skip_value_anim <- value.constant(()).gate(&small_value_step); - skip_value_anim <- skip_value_anim.gate(&value_adjust).gate_not(&prec_adjust); - model.value_animation.skip <+ skip_value_anim; + + // === Value Reset === + + reset_value <- ptr_down.gate(&keyboard.is_control_down); + value_on_reset <- input.set_default_value.sample(&reset_value); + output.value <+ value_on_reset; + + + // === Value Animation === + model.value_animation.target <+ output.value; }; } @@ -558,35 +495,35 @@ impl Slider { let model = &self.model; frp::extend! { network - min_value <- all5( + min_value <- all_with5( &output.value, &input.set_min_value, &input.set_max_value, &output.min_value, &input.set_lower_limit_type, - ); - min_value <- min_value.map(adapt_lower_limit).on_change(); + |a,b,c,d,e| adapt_lower_limit(*a,*b,*c,*d,*e) + ).on_change(); output.min_value <+ min_value; - max_value<- all5( + + max_value <- all_with5( &output.value, &input.set_min_value, &input.set_max_value, &output.max_value, &input.set_upper_limit_type, - ); - max_value <- max_value.map(adapt_upper_limit).on_change(); + |a,b,c,d,e|adapt_upper_limit(*a,*b,*c,*d,*e) + ).on_change(); output.max_value <+ max_value; - overflow_lower <- all2(&output.value, &output.min_value).map(|(val, min)| val < min ); - overflow_upper <- all2(&output.value, &output.max_value).map(|(val, max)| val > max ); - overflow_lower <- overflow_lower.on_change(); - overflow_upper <- overflow_upper.on_change(); + overflow_lower <- all_with(&output.value, &min_value, |v, min| v < min).on_change(); + overflow_upper <- all_with(&output.value, &max_value, |v, max| v > max).on_change(); eval overflow_lower((v) model.set_overflow_lower_visible(*v)); eval overflow_upper((v) model.set_overflow_upper_visible(*v)); }; } - /// Initialize the value display FRP network. + /// Initialize the value display FRP network. Sets text to bold if the value is not the default + /// one and manages the value display on the slider. fn init_value_display(&self) { let network = self.frp.network(); let input = &self.frp.input; @@ -594,24 +531,24 @@ impl Slider { let model = &self.model; frp::extend! { network - eval input.set_value_text_hidden((v) model.set_value_text_hidden(*v)); - value <- output.value.gate_not(&input.set_value_text_hidden).on_change(); - precision <- output.precision.gate_not(&input.set_value_text_hidden).on_change(); - value_is_default <- all2(&value, &input.set_default_value).map(|(val, def)| val==def); - value_is_default_true <- value_is_default.on_true(); - value_is_default_false <- value_is_default.on_false(); - eval_ value_is_default_true(model.set_value_text_property(formatting::Weight::Normal)); - eval_ value_is_default_false(model.set_value_text_property(formatting::Weight::Bold)); + eval input.show_value((v) model.show_value(*v)); - value_text_left_right <- all3(&value, &precision, &input.set_max_disp_decimal_places); - value_text_left_right <- value_text_left_right.map(value_text_truncate_split); - value_text_left <- value_text_left_right._0(); - value_text_right <- value_text_left_right._1(); - model.value_text_left.set_content <+ value_text_left; - value_text_right_is_visible <- value_text_right.map(|t| t.is_some()).on_change(); - value_text_right <- value_text_right.gate(&value_text_right_is_visible); - model.value_text_right.set_content <+ value_text_right.unwrap(); - eval value_text_right_is_visible((v) model.set_value_text_right_visible(*v)); + value <- output.value.sampled_gate(&input.show_value); + default_value <- input.set_default_value.sampled_gate(&input.show_value); + is_default <- all_with(&value, &default_value, |val, def| val == def); + text_weight <- switch_constant(&is_default, Weight::Bold, Weight::Normal); + eval text_weight ((v) model.set_value_text_property(*v)); + + precision <- output.precision.sampled_gate(&input.show_value); + 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_left <- text._0(); + text_right <- text._1(); + model.value_text_left.set_content <+ text_left; + text_right_visible <- text_right.map(|t| t.is_some()).on_change(); + new_text_right <= text_right.gate(&text_right_visible); + model.value_text_right.set_content <+ new_text_right; + eval text_right_visible((v) model.set_value_text_right_visible(*v)); }; } @@ -689,17 +626,19 @@ impl Slider { let min_limit_anim = Animation::new_non_init(network); let max_limit_anim = Animation::new_non_init(network); + let obj = model.display_object(); + frp::extend! { network - comp_size <- all2(&input.set_width, &input.set_height).map(|(w, h)| Vector2(*w,*h)); - eval comp_size((size) model.update_size(*size)); - eval input.set_value_indicator((i) model.set_value_indicator(i)); - output.width <+ input.set_width; - output.height <+ input.set_height; + // comp_size <- all2(&input.set_width, &input.set_height).map(|(w, h)| Vector2(*w,*h)); + eval obj.on_resized((size) model.update_size(*size)); + eval input.kind((i) model.kind(i)); + // output.width <+ input.set_width; + // output.height <+ input.set_height; min_limit_anim.target <+ output.min_value; max_limit_anim.target <+ output.max_value; indicator_pos <- all3(&model.value_animation.value, &min_limit_anim.value, &max_limit_anim.value); indicator_pos <- indicator_pos.map(|(value, min, max)| (value - min) / (max - min)); - indicator_pos <- all3(&indicator_pos, &input.set_thumb_size, &input.set_orientation); + indicator_pos <- all3(&indicator_pos, &input.set_thumb_size, &input.orientation); eval indicator_pos((v) model.set_indicator_position(v)); value_text_left_pos_x <- all3( @@ -722,28 +661,28 @@ impl Slider { eval model.value_text_edit.width((w) model.value_text_edit.set_x(-*w / 2.0)); eval model.value_text_edit.height((h) model.value_text_edit.set_y(*h / 2.0)); - overflow_marker_position <- all3( - &input.set_width, - &input.set_height, - &input.set_orientation, - ); - eval overflow_marker_position((p) model.set_overflow_marker_position(p)); - overflow_marker_shape <- all2(&model.value_text_left.height, &input.set_orientation); - eval overflow_marker_shape((s) model.set_overflow_marker_shape(s)); + // overflow_marker_position <- all3( + // &input.set_width, + // &input.set_height, + // &input.orientation, + // ); + // eval overflow_marker_position((p) model.set_overflow_marker_position(p)); + // overflow_marker_shape <- all2(&model.value_text_left.height, &input.orientation); + // eval overflow_marker_shape((s) model.set_overflow_marker_shape(s)); + // + // eval input.set_label_hidden((v) model.set_label_hidden(*v)); + // model.label.set_content <+ input.set_label; + // label_position <- all6( + // &input.set_width, + // &input.set_height, + // &model.label.width, + // &model.label.height, + // &input.set_label_position, + // &input.orientation, + // ); + // eval label_position((p) model.set_label_position(p)); - eval input.set_label_hidden((v) model.set_label_hidden(*v)); - model.label.set_content <+ input.set_label; - label_position <- all6( - &input.set_width, - &input.set_height, - &model.label.width, - &model.label.height, - &input.set_label_position, - &input.set_orientation, - ); - eval label_position((p) model.set_label_position(p)); - - output.orientation <+ input.set_orientation; + // output.orientation <+ input.orientation; }; } @@ -785,7 +724,7 @@ impl Slider { frp::extend! { network start_editing <- input.start_value_editing.gate_not(&output.disabled); - start_editing <- start_editing.gate_not(&input.set_value_text_hidden); + start_editing <- start_editing.gate(&input.show_value); value_on_edit <- output.value.sample(&start_editing); prec_on_edit <- output.precision.sample(&start_editing); max_places_on_edit <- @@ -835,6 +774,8 @@ impl Slider { self.frp.set_tooltip_delay(INFORMATION_TOOLTIP_DELAY); self.frp.set_precision_popup_duration(PRECISION_ADJUSTMENT_POPUP_DURATION); self.frp.set_thumb_size(THUMB_SIZE_DEFAULT); + self.show_value(true); + self.orientation(Axis2::X); } } @@ -904,9 +845,7 @@ 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 /// digits left of the decimal point, and one optional with the digits right of the decimal point. -fn value_text_truncate_split( - (value, precision, max_digits): &(f32, f32, usize), -) -> (ImString, Option) { +fn display_value(value: &f32, precision: &f32, max_digits: &usize) -> (ImString, Option) { let text = value_text_truncate(&(*value, *precision, *max_digits)); let mut text_iter = text.split('.'); let text_left = text_iter.next().map(|s| s.to_im_string()).unwrap_or_default(); @@ -953,42 +892,42 @@ mod tests { #[test] fn test_high_precision() { - let (left, right) = value_text_truncate_split(&(123.4567, 0.01, 8)); + let (left, right) = display_value(&123.4567, &0.01, &8); assert_eq!(left, "123".to_im_string()); assert_eq!(right, Some("46".to_im_string())); } #[test] fn test_low_precision() { - let (left, right) = value_text_truncate_split(&(123.4567, 10.0, 8)); + let (left, right) = display_value(&123.4567, &10.0, &8); assert_eq!(left, "123".to_im_string()); assert_eq!(right, None); } #[test] fn test_precision_is_zero() { - let (left, right) = value_text_truncate_split(&(123.4567, 0.0, 8)); + let (left, right) = display_value(&123.4567, &0.0, &8); assert_eq!(left, "123".to_im_string()); assert_eq!(right, Some("45670319".to_im_string())); } #[test] fn test_precision_is_nan() { - let (left, right) = value_text_truncate_split(&(123.4567, NAN, 8)); + let (left, right) = display_value(&123.4567, &NAN, &8); assert_eq!(left, "123".to_im_string()); assert_eq!(right, None); } #[test] fn test_value_is_nan() { - let (left, right) = value_text_truncate_split(&(NAN, 0.01, 8)); + let (left, right) = display_value(&NAN, &0.01, &8); assert_eq!(left, "NaN".to_im_string()); assert_eq!(right, None); } #[test] fn test_zero_decimal_places() { - let (left, right) = value_text_truncate_split(&(123.4567, 0.01, 0)); + let (left, right) = display_value(&123.4567, &0.01, &0); assert_eq!(left, "123".to_im_string()); assert_eq!(right, None); } diff --git a/lib/rust/ensogl/component/slider/src/model.rs b/lib/rust/ensogl/component/slider/src/model.rs index 5e9195514e..82a217e8dc 100644 --- a/lib/rust/ensogl/component/slider/src/model.rs +++ b/lib/rust/ensogl/component/slider/src/model.rs @@ -3,9 +3,8 @@ use ensogl_core::display::shape::*; use ensogl_core::prelude::*; +use crate::Kind; use crate::LabelPosition; -use crate::SliderOrientation; -use crate::ValueIndicator; use ensogl_core::application::Application; use ensogl_core::data::color; @@ -264,13 +263,13 @@ impl Model { } /// Set whether the lower overfow marker is visible. - pub fn set_value_indicator(&self, indicator: &ValueIndicator) { + pub fn kind(&self, indicator: &Kind) { match indicator { - ValueIndicator::Track => { + Kind::SingleValue => { self.root.add_child(&self.track); self.root.remove_child(&self.thumb); } - ValueIndicator::Thumb => { + Kind::Scrollbar(_) => { self.root.add_child(&self.thumb); self.root.remove_child(&self.track); } @@ -278,19 +277,16 @@ impl Model { } /// Set the position of the value indicator. - pub fn set_indicator_position( - &self, - (fraction, size, orientation): &(f32, f32, SliderOrientation), - ) { + pub fn set_indicator_position(&self, (fraction, size, orientation): &(f32, f32, Axis2)) { self.thumb.slider_fraction.set(*fraction); match orientation { - SliderOrientation::Horizontal => { + Axis2::X => { self.track.slider_fraction_horizontal.set(fraction.clamp(0.0, 1.0)); self.track.slider_fraction_vertical.set(1.0); self.thumb.thumb_width.set(*size); self.thumb.thumb_height.set(1.0); } - SliderOrientation::Vertical => { + Axis2::Y => { self.track.slider_fraction_horizontal.set(1.0); self.track.slider_fraction_vertical.set(fraction.clamp(0.0, 1.0)); self.thumb.thumb_width.set(1.0); @@ -300,17 +296,17 @@ impl Model { } /// Set the size and orientation of the overflow markers. - pub fn set_overflow_marker_shape(&self, (size, orientation): &(f32, SliderOrientation)) { + pub fn set_overflow_marker_shape(&self, (size, orientation): &(f32, Axis2)) { let margin = Vector2(COMPONENT_MARGIN * 2.0, COMPONENT_MARGIN * 2.0); let size = Vector2(*size, *size) * OVERFLOW_MARKER_SIZE + margin; self.overflow_lower.set_size(size); self.overflow_upper.set_size(size); match orientation { - SliderOrientation::Horizontal => { + Axis2::X => { self.overflow_lower.set_rotation_z(std::f32::consts::FRAC_PI_2); self.overflow_upper.set_rotation_z(-std::f32::consts::FRAC_PI_2); } - SliderOrientation::Vertical => { + Axis2::Y => { self.overflow_lower.set_rotation_z(std::f32::consts::PI); self.overflow_upper.set_rotation_z(0.0); } @@ -320,17 +316,17 @@ impl Model { /// Set the position of the overflow markers. pub fn set_overflow_marker_position( &self, - (comp_width, comp_height, orientation): &(f32, f32, SliderOrientation), + (comp_width, comp_height, orientation): &(f32, f32, Axis2), ) { match orientation { - SliderOrientation::Horizontal => { + Axis2::X => { let pos_x = comp_width / 2.0 - comp_height / 4.0; self.overflow_lower.set_x(-pos_x); self.overflow_lower.set_y(0.0); self.overflow_upper.set_x(pos_x); self.overflow_upper.set_y(0.0); } - SliderOrientation::Vertical => { + Axis2::Y => { let pos_y = comp_height / 2.0 - comp_width / 4.0; self.overflow_lower.set_x(0.0); self.overflow_lower.set_y(-pos_y); @@ -367,19 +363,19 @@ impl Model { f32, f32, LabelPosition, - SliderOrientation, + Axis2, ), ) { let label_position_x = match orientation { - SliderOrientation::Horizontal => match position { + Axis2::X => match position { LabelPosition::Inside => -comp_width / 2.0 + comp_height / 2.0, LabelPosition::Outside => -comp_width / 2.0 - comp_height / 2.0 - lab_width, }, - SliderOrientation::Vertical => -lab_width / 2.0, + Axis2::Y => -lab_width / 2.0, }; let label_position_y = match orientation { - SliderOrientation::Horizontal => lab_height / 2.0, - SliderOrientation::Vertical => match position { + Axis2::X => lab_height / 2.0, + Axis2::Y => match position { LabelPosition::Inside => comp_height / 2.0 - comp_width / 2.0, LabelPosition::Outside => comp_height / 2.0 + comp_width / 2.0 + lab_height, }, @@ -388,15 +384,15 @@ impl Model { } /// Set whether the slider value text is hidden. - pub fn set_value_text_hidden(&self, hidden: bool) { - if hidden { - self.root.remove_child(&self.value_text_left); - self.root.remove_child(&self.value_text_dot); - self.root.remove_child(&self.value_text_right); - } else { + pub fn show_value(&self, visible: bool) { + if visible { self.root.add_child(&self.value_text_left); self.root.add_child(&self.value_text_dot); self.root.add_child(&self.value_text_right); + } else { + self.root.remove_child(&self.value_text_left); + self.root.remove_child(&self.value_text_dot); + self.root.remove_child(&self.value_text_right); } } diff --git a/lib/rust/ensogl/core/src/gui/cursor.rs b/lib/rust/ensogl/core/src/gui/cursor.rs index bc0602abf3..191f433746 100644 --- a/lib/rust/ensogl/core/src/gui/cursor.rs +++ b/lib/rust/ensogl/core/src/gui/cursor.rs @@ -54,6 +54,7 @@ define_style! { press: f32, port_selection_layer : bool, trash: f32, + plus: f32, } @@ -106,6 +107,11 @@ impl Style { let trash = Some(StyleValue::new(1.0)); Self { trash, ..default() } } + + pub fn plus() -> Self { + let plus = Some(StyleValue::new(1.0)); + Self { plus, ..default() } + } } @@ -153,6 +159,7 @@ pub mod shape { radius: f32, color: Vector4, trash: f32, + plus: f32, ) { let width : Var = "input_size.x".into(); let height : Var = "input_size.y".into(); @@ -167,13 +174,24 @@ pub mod shape { let color: Var = color.into(); let trash_color: Var = color::Rgba::new(0.91, 0.32, 0.32, 1.0).into(); let color = color.mix(&trash_color, &trash); + + let plus_color: Var = color::Rgba::new(0.39, 0.71, 0.15, 1.0).into(); + let color = color.mix(&plus_color, &plus); + + let cursor = cursor.fill(color); let trash_bar1 = Rect((2.px(), (&height - 4.px()) * &trash - 1.px())); let trash_bar2 = trash_bar1.rotate((PI/2.0).radians()); let trash_bar_x = (trash_bar1 + trash_bar2).rotate((PI/4.0).radians()); let trash_bar_x = trash_bar_x.fill(color::Rgba::new(1.0,1.0,1.0,0.8)); - let cursor = cursor + trash_bar_x; + + let plus_bar1 = Rect((2.px(), (&height - 4.px()) * &plus - 1.px())); + let plus_bar2 = plus_bar1.rotate((PI/2.0).radians()); + let plus_sign = plus_bar1 + plus_bar2; + let plus_sign = plus_sign.fill(color::Rgba::new(1.0,1.0,1.0,0.8)); + + let cursor = cursor + trash_bar_x + plus_sign; cursor.into() } } @@ -298,6 +316,7 @@ impl Cursor { let host_attached_weight = Easing::new(network); let port_selection_layer_weight = Animation::::new(network); let trash = Animation::::new(network); + let plus = Animation::::new(network); host_attached_weight.set_duration(300.0); color_lab.set_target_value(DEFAULT_COLOR.opaque.into()); @@ -316,6 +335,7 @@ impl Cursor { model.for_each_view(|vw| {vw.set_size(dim);}); }); eval trash.value ((v) model.for_each_view(|vw| vw.trash.set(*v))); + eval plus.value ((v) model.for_each_view(|vw| vw.plus.set(*v))); alpha <- all_with(&color_alpha.value,&inactive_fade.value,|s,t| s*t); @@ -394,6 +414,11 @@ impl Cursor { Some(t) => trash.target.emit(t.value.unwrap_or(0.0)), } + match &new_style.plus { + None => plus.target.emit(0.0), + Some(t) => plus.target.emit(t.value.unwrap_or(0.0)), + } + *model.style.borrow_mut() = new_style.clone(); }); diff --git a/lib/rust/ensogl/examples/slider/src/lib.rs b/lib/rust/ensogl/examples/slider/src/lib.rs index 503946f27b..8b01e8c83a 100644 --- a/lib/rust/ensogl/examples/slider/src/lib.rs +++ b/lib/rust/ensogl/examples/slider/src/lib.rs @@ -29,6 +29,7 @@ use ensogl_core::application::Application; use ensogl_core::application::View; use ensogl_core::data::color; use ensogl_core::display; +use ensogl_core::display::navigation::navigator::Navigator; use ensogl_slider as slider; use ensogl_text_msdf::run_once_initialized; @@ -41,10 +42,10 @@ use ensogl_text_msdf::run_once_initialized; /// Create a basic slider. fn make_slider(app: &Application) -> slider::Slider { let slider = app.new_view::(); - slider.frp.set_background_color(color::Lcha(0.8, 0.0, 0.0, 1.0)); - slider.frp.set_max_value(5.0); - slider.frp.set_default_value(1.0); - slider.frp.set_value(1.0); + // slider.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 } @@ -58,17 +59,22 @@ fn make_slider(app: &Application) -> slider::Slider { #[derive(Debug, Clone, CloneRef)] pub struct Model { /// Vector that holds example sliders until they are dropped. - sliders: Rc>>, - app: Application, - root: display::object::Instance, + sliders: Rc>>, + app: Application, + root: display::object::Instance, + navigator: Navigator, } impl Model { fn new(app: &Application) -> Self { let app = app.clone_ref(); + let world = app.display.clone(); + let scene = &world.default_scene; + let camera = scene.camera().clone_ref(); + let navigator = Navigator::new(scene, &camera); let sliders = Rc::new(RefCell::new(Vec::new())); let root = display::object::Instance::new(); - let model = Self { app, sliders, root }; + let model = Self { app, sliders, root, navigator }; model.init_sliders(); model } @@ -76,8 +82,7 @@ impl Model { /// Add example sliders to scene. fn init_sliders(&self) { let slider1 = make_slider(&self.app); - slider1.frp.set_width(400.0); - slider1.frp.set_height(50.0); + slider1.set_size((200.0, 24.0)); slider1.set_y(-120.0); slider1.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); slider1.frp.set_label("Soft limits + tooltip"); @@ -88,8 +93,7 @@ impl Model { self.sliders.borrow_mut().push(slider1); let slider2 = make_slider(&self.app); - slider2.frp.set_width(400.0); - slider2.frp.set_height(50.0); + slider2.set_size((400.0, 50.0)); slider2.set_y(-60.0); slider2.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); slider2.frp.set_slider_disabled(true); @@ -98,8 +102,7 @@ impl Model { self.sliders.borrow_mut().push(slider2); let slider3 = make_slider(&self.app); - slider3.frp.set_width(400.0); - slider3.frp.set_height(50.0); + slider3.set_size((400.0, 50.0)); slider3.set_y(0.0); slider3.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); slider3.frp.set_default_value(100.0); @@ -111,8 +114,7 @@ impl Model { self.sliders.borrow_mut().push(slider3); let slider4 = make_slider(&self.app); - slider4.frp.set_width(400.0); - slider4.frp.set_height(50.0); + slider4.set_size((400.0, 50.0)); slider4.set_y(60.0); slider4.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); slider4.frp.set_label("Adaptive upper limit"); @@ -122,20 +124,18 @@ impl Model { self.sliders.borrow_mut().push(slider4); let slider5 = make_slider(&self.app); - slider5.frp.set_width(75.0); - slider5.frp.set_height(230.0); + slider5.set_size((75.0, 230.0)); slider5.set_y(-35.0); slider5.set_x(275.0); slider5.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); slider5.frp.set_label("Hard limits"); - slider5.frp.set_orientation(slider::SliderOrientation::Vertical); + slider5.frp.orientation(Axis2::Y); slider5.frp.set_max_disp_decimal_places(4); self.root.add_child(&slider5); self.sliders.borrow_mut().push(slider5); let slider6 = make_slider(&self.app); - slider6.frp.set_width(75.0); - slider6.frp.set_height(230.0); + slider6.set_size((75.0, 230.0)); slider6.set_y(-35.0); slider6.set_x(375.0); slider6.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); @@ -143,57 +143,53 @@ impl Model { slider6.frp.set_label_position(slider::LabelPosition::Inside); slider6.frp.set_lower_limit_type(slider::SliderLimit::Soft); slider6.frp.set_upper_limit_type(slider::SliderLimit::Soft); - slider6.frp.set_orientation(slider::SliderOrientation::Vertical); + slider6.frp.orientation(Axis2::Y); slider6.frp.set_max_disp_decimal_places(4); self.root.add_child(&slider6); self.sliders.borrow_mut().push(slider6); let slider7 = make_slider(&self.app); - slider7.frp.set_width(400.0); - slider7.frp.set_height(10.0); + slider7.set_size((400.0, 10.0)); slider7.set_y(-160.0); slider7.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider7.frp.set_value_text_hidden(true); + slider7.frp.show_value(false); slider7.frp.set_precision_adjustment_disabled(true); - slider7.frp.set_value_indicator(slider::ValueIndicator::Thumb); + slider7.frp.kind(slider::Kind::Scrollbar(0.1)); slider7.frp.set_thumb_size(0.1); self.root.add_child(&slider7); self.sliders.borrow_mut().push(slider7); let slider8 = make_slider(&self.app); - slider8.frp.set_width(400.0); - slider8.frp.set_height(10.0); + slider8.set_size((400.0, 10.0)); slider8.set_y(-180.0); slider8.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider8.frp.set_value_text_hidden(true); + slider8.frp.show_value(false); slider8.frp.set_precision_adjustment_disabled(true); - slider8.frp.set_value_indicator(slider::ValueIndicator::Thumb); + slider8.frp.kind(slider::Kind::Scrollbar(0.25)); slider8.frp.set_thumb_size(0.25); self.root.add_child(&slider8); self.sliders.borrow_mut().push(slider8); let slider9 = make_slider(&self.app); - slider9.frp.set_width(400.0); - slider9.frp.set_height(10.0); + slider9.set_size((400.0, 10.0)); slider9.set_y(-200.0); slider9.frp.set_value_indicator_color(color::Lcha(0.4, 0.7, 0.7, 1.0)); - slider9.frp.set_value_text_hidden(true); + slider9.frp.show_value(false); slider9.frp.set_precision_adjustment_disabled(true); - slider9.frp.set_value_indicator(slider::ValueIndicator::Thumb); + slider9.frp.kind(slider::Kind::Scrollbar(0.5)); slider9.frp.set_thumb_size(0.5); self.root.add_child(&slider9); self.sliders.borrow_mut().push(slider9); let slider10 = make_slider(&self.app); - slider10.frp.set_width(10.0); - slider10.frp.set_height(230.0); + 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.set_value_text_hidden(true); + slider10.frp.show_value(false); slider10.frp.set_precision_adjustment_disabled(true); - slider10.frp.set_value_indicator(slider::ValueIndicator::Thumb); - slider10.frp.set_orientation(slider::SliderOrientation::Vertical); + slider10.frp.kind(slider::Kind::Scrollbar(0.1)); + slider10.frp.orientation(Axis2::Y); self.root.add_child(&slider10); self.sliders.borrow_mut().push(slider10); } diff --git a/lib/rust/frp/src/nodes.rs b/lib/rust/frp/src/nodes.rs index 1d3fc45a83..2f4ef9a790 100644 --- a/lib/rust/frp/src/nodes.rs +++ b/lib/rust/frp/src/nodes.rs @@ -126,6 +126,22 @@ impl Network { self.register(OwnedGate::new(label, event, behavior)) } + pub fn sampled_gate( + &self, + label: Label, + event: &T1, + behavior: &T2, + ) -> Stream> + where + T1: EventOutput, + T2: EventOutput, + { + let value = self.gate(label, event, behavior); + let on_gate_pass = self.on_true(label, behavior); + let value2 = self.sample(label, event, &on_gate_pass); + self.any(label, &value, &value2) + } + /// Like `gate` but passes the value when the condition is `false`. pub fn gate_not(&self, label: Label, event: &T1, behavior: &T2) -> Stream> where @@ -366,6 +382,16 @@ impl Network { self.all_with(label, t1, t2, |a, b| *a && *b) } + pub fn is_some(&self, label: Label, src: &T) -> Stream + where T: EventOutput> { + self.map(label, src, |t| t.is_some()) + } + + pub fn is_none(&self, label: Label, src: &T) -> Stream + where T: EventOutput> { + self.map(label, src, |t| t.is_none()) + } + /// Redirect second or third input to the output when the value of the first input is `false` or /// `true` respectively. The redirection is persistent. The first input doesn't have to fire to /// propagate the events fromm second and third input streams. Moreover, when first input @@ -1067,6 +1093,42 @@ impl Network { self.register(OwnedAllWith6::new(label, t1, t2, t3, t4, t5, t6, f)) } + /// Specialized version `all_with`. + pub fn all_with7( + &self, + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + f: F, + ) -> Stream + where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + T: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> T, + { + self.register(OwnedAllWith7::new(label, t1, t2, t3, t4, t5, t6, t7, f)) + } + /// Specialized version `all_with`. pub fn all_with8( &self, @@ -4321,6 +4383,147 @@ impl Debug for AllWith6Data { + src1: watch::Ref, + src2: watch::Ref, + src3: watch::Ref, + src4: watch::Ref, + src5: watch::Ref, + src6: watch::Ref, + src7: watch::Ref, + function: F, +} +pub type OwnedAllWith7 = + stream::Node>; +pub type AllWith7 = + stream::WeakNode>; + +impl HasOutput for AllWith7Data +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + type Output = Out; +} + +impl OwnedAllWith7 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + /// Constructor. + pub fn new( + label: Label, + t1: &T1, + t2: &T2, + t3: &T3, + t4: &T4, + t5: &T5, + t6: &T6, + t7: &T7, + function: F, + ) -> Self { + let src1 = watch_stream(t1); + 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 = AllWith7Data { src1, src2, src3, src4, src5, src6, src7, function }; + let this = Self::construct(label, def); + let weak = this.downgrade(); + t1.register_target(weak.clone_ref().into()); + t2.register_target(weak.clone_ref().into()); + t3.register_target(weak.clone_ref().into()); + t4.register_target(weak.clone_ref().into()); + t5.register_target(weak.clone_ref().into()); + t6.register_target(weak.clone_ref().into()); + t7.register_target(weak.into()); + this + } +} + +impl stream::EventConsumer + for OwnedAllWith7 +where + T1: EventOutput, + T2: EventOutput, + T3: EventOutput, + T4: EventOutput, + T5: EventOutput, + T6: EventOutput, + T7: EventOutput, + Out: Data, + F: 'static + + Fn( + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + &Output, + ) -> Out, +{ + fn on_event(&self, stack: CallStack, _: &T) { + let value1 = self.src1.value(); + 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 Debug for AllWith7Data { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "AllWith8Data") + } +} + + + // ================ // === AllWith8 === // ================ diff --git a/lib/rust/prelude/src/option.rs b/lib/rust/prelude/src/option.rs index 8f18b5f308..47bb61789e 100644 --- a/lib/rust/prelude/src/option.rs +++ b/lib/rust/prelude/src/option.rs @@ -13,6 +13,10 @@ pub trait OptionOps { where U: Default, F: FnOnce(Self::Item) -> U; + fn if_some_or_default(self, f: F) -> U + where + U: Default, + F: FnOnce() -> U; fn map_ref_or_default(&self, f: F) -> U where U: Default, @@ -51,6 +55,13 @@ impl OptionOps for Option { self.map_or_else(U::default, f) } + fn if_some_or_default(self, f: F) -> U + where + U: Default, + F: FnOnce() -> U, { + self.map_or_else(U::default, |_| f()) + } + fn map_ref_or_default(&self, f: F) -> U where U: Default, diff --git a/lib/rust/types/src/dim.rs b/lib/rust/types/src/dim.rs index 6c142d1da8..548bffa3b6 100644 --- a/lib/rust/types/src/dim.rs +++ b/lib/rust/types/src/dim.rs @@ -342,6 +342,30 @@ pub struct Z; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct W; +/// An axis in 2D space. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[allow(missing_docs)] +pub enum Axis2 { + X, + Y, +} + +impl Default for Axis2 { + fn default() -> Self { + Self::X + } +} + +impl Axis2 { + /// The orthogonal axis to the current one. + pub fn orthogonal(self) -> Self { + match self { + Self::X => Self::Y, + Self::Y => Self::X, + } + } +} + /// Component getter for the given dimension. #[allow(missing_docs)] pub trait Dim: Dim1 { @@ -432,6 +456,79 @@ gen_dim_impl_for_vector!(Vector2, x, y); gen_dim_impl_for_vector!(Vector3, x, y, z); gen_dim_impl_for_vector!(Vector4, x, y, z, w); +impl Dim for Vector2 { + fn get_dim(&self, dim: Axis2) -> Self::Dim1Type { + match dim { + Axis2::X => self.x(), + Axis2::Y => self.y(), + } + } +} + +impl DimSetter for Vector2 { + fn set_dim(&mut self, dim: Axis2, value: Self::Dim1Type) { + match dim { + Axis2::X => self.set_x(value), + Axis2::Y => self.set_y(value), + } + } + + fn set_dim_checked(&mut self, dim: Axis2, value: Self::Dim1Type) -> bool { + match dim { + Axis2::X => + if self.x() == value { + false + } else { + self.set_x(value); + true + }, + Axis2::Y => + if self.y() == value { + false + } else { + self.set_y(value); + true + }, + } + } +} + +impl Dim<&Axis2> for Vector2 { + fn get_dim(&self, dim: &Axis2) -> Self::Dim1Type { + match dim { + Axis2::X => self.x(), + Axis2::Y => self.y(), + } + } +} + +impl DimSetter<&Axis2> for Vector2 { + fn set_dim(&mut self, dim: &Axis2, value: Self::Dim1Type) { + match dim { + Axis2::X => self.set_x(value), + Axis2::Y => self.set_y(value), + } + } + + fn set_dim_checked(&mut self, dim: &Axis2, value: Self::Dim1Type) -> bool { + match dim { + Axis2::X => + if self.x() == value { + false + } else { + self.set_x(value); + true + }, + Axis2::Y => + if self.y() == value { + false + } else { + self.set_y(value); + true + }, + } + } +} // =====================================================