Implementation of elements adding to List Editor and a lot of internal API (#6390)

This commit is contained in:
Wojciech Daniło 2023-04-25 18:06:11 +02:00 committed by GitHub
parent 952de24e79
commit 115e9b4ffd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 879 additions and 456 deletions

View File

@ -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! { <T: ('static)>
/// 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<T>)>),
// on_item_removed(Response<(Index, Weak<T>)>),
on_item_removed(Response<(Index, Weak<T>)>),
/// 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! { <T: ('static)>
#[derivative(Clone(bound = ""))]
pub struct ListEditor<T: 'static> {
#[deref]
pub frp: Frp<T>,
root: display::object::Instance,
model: SharedModel<T>,
add_elem_icon: Rectangle,
remove_elem_icon: Rectangle,
pub frp: Frp<T>,
root: display::object::Instance,
model: SharedModel<T>,
}
#[derive(Debug)]
pub struct Model<T> {
cursor: Cursor,
items: VecIndexedBy<ItemOrPlaceholder<T>, ItemOrPlaceholderIndex>,
root: display::object::Instance,
layout: display::object::Instance,
gap: f32,
}
impl<T> Model<T> {
/// 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<T> Default for Model<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Derivative, CloneRef, Debug, Default, Deref)]
#[derive(Derivative, CloneRef, Debug, Deref)]
#[derivative(Clone(bound = ""))]
pub struct SharedModel<T> {
rc: Rc<RefCell<Model<T>>>,
@ -342,31 +339,13 @@ impl<T> From<Model<T>> for SharedModel<T> {
}
impl<T: display::Object + Clone + 'static> ListEditor<T> {
impl<T: display::Object + CloneRef> ListEditor<T> {
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<T: display::Object + Clone + 'static> ListEditor<T> {
let network = self.frp.network();
let model = &self.model;
let add_elem_icon_down = self.add_elem_icon.on_event::<mouse::Down>();
let remove_elem_icon_down = self.remove_elem_icon.on_event::<mouse::Down>();
let on_down = model.borrow().layout.on_event_capturing::<mouse::Down>();
let on_up_source = scene.on_event::<mouse::Up>();
let on_move = scene.on_event::<mouse::Move>();
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<T: display::Object + Clone + 'static> ListEditor<T> {
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<Event<mouse::Up>>,
pos_on_move: &frp::Stream<Vector2>,
is_dragging: &frp::Stream<bool>,
) -> frp::Stream<cursor::Style> {
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<Event<mouse::Up>>,
on_down: &frp::Stream<Event<mouse::Down>>,
target: &frp::Stream<display::object::Instance>,
pos_diff: &frp::Stream<Vector2>,
) -> (frp::Stream<bool>, frp::Stream<Vector2>) {
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<T: display::Object + Clone + 'static> ListEditor<T> {
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<Event<mouse::Up>>,
drag_diff: &frp::Stream<Vector2>,
) -> frp::Stream<bool> {
) -> (frp::Stream<bool>, frp::Stream<cursor::Style>) {
let on_up = on_up.clone_ref();
let drag_diff = drag_diff.clone_ref();
let model = &self.model;
@ -502,21 +532,61 @@ impl<T: display::Object + Clone + 'static> ListEditor<T> {
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<Event<mouse::Up>>,
pos_on_move: &frp::Stream<Vector2>,
is_trashing: &frp::Stream<bool>,
) {
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(&center_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<T: display::Object + Clone + 'static> ListEditor<T> {
}
}
impl<T: display::Object + 'static> SharedModel<T> {
impl<T: display::Object + CloneRef + 'static> SharedModel<T> {
fn screen_to_object_space(&self, screen_pos: Vector2) -> Vector2 {
self.borrow().screen_to_object_space(screen_pos)
}
@ -537,9 +607,52 @@ impl<T: display::Object + 'static> SharedModel<T> {
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<T>) -> Option<Index> {
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<T>) -> Option<Index> {
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<f32> {
self.borrow().center_points()
}
fn item_or_placeholder_index_to_index(&self, ix: ItemOrPlaceholderIndex) -> Option<Index> {
self.borrow().item_or_placeholder_index_to_index(ix)
}
}
impl<T: display::Object + 'static> Model<T> {
#[derive(Clone, Debug, Default, Deref)]
pub struct Gaps {
gaps: Vec<RangeInclusive<f32>>,
}
impl Gaps {
pub fn find(&self, x: f32) -> Option<ItemOrPlaceholderIndex> {
self.gaps.iter().position(|gap| gap.contains(&x)).map(|t| t.into())
}
}
impl<T: display::Object + CloneRef + 'static> Model<T> {
// FIXME: refactor and generalize
fn screen_to_object_space(&self, screen_pos: Vector2) -> Vector2 {
let scene = scene();
@ -561,18 +674,41 @@ impl<T: display::Object + 'static> Model<T> {
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<ItemOrPlaceholderIndex> {
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<ItemOrPlaceholderIndex> {
fn index_to_item_or_placeholder_index(&self, ix: Index) -> Option<ItemOrPlaceholderIndex> {
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<Index> {
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<Index> {
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<T: display::Object + 'static> Model<T> {
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<T: display::Object + 'static> Model<T> {
/// 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<T: display::Object + 'static> Model<T> {
/// 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<Index> {
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<T: display::Object + 'static> Model<T> {
/// │ 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<T> {
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<T> {
@ -788,8 +920,10 @@ impl<T: display::Object + 'static> Model<T> {
/// 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| 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| 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<T: display::Object + 'static> Model<T> {
/// 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::<T>() {
fn place_dragged_item(&mut self, index: ItemOrPlaceholderIndex) -> Option<(Index, T)> {
if let Some(item) = self.cursor.stop_drag_if_is::<T>() {
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<T: display::Object + 'static> Model<T> {
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::<T>() {
if let Some(item) = self.cursor.stop_drag_if_is::<T>() {
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<T> {
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<T: display::Object + 'static> Model<T> {
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);

View File

@ -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::<mouse::Down>();
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();
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<ImString>) {
fn display_value(value: &f32, precision: &f32, max_digits: &usize) -> (ImString, Option<ImString>) {
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);
}

View File

@ -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);
}
}

View File

@ -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<Pixels> = "input_size.x".into();
let height : Var<Pixels> = "input_size.y".into();
@ -167,13 +174,24 @@ pub mod shape {
let color: Var<color::Rgba> = color.into();
let trash_color: Var<color::Rgba> = 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> = 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::<f32>::new(network);
let trash = Animation::<f32>::new(network);
let plus = Animation::<f32>::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();
});

View File

@ -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::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.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<RefCell<Vec<slider::Slider>>>,
app: Application,
root: display::object::Instance,
sliders: Rc<RefCell<Vec<slider::Slider>>>,
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);
}

View File

@ -126,6 +126,22 @@ impl Network {
self.register(OwnedGate::new(label, event, behavior))
}
pub fn sampled_gate<T1, T2>(
&self,
label: Label,
event: &T1,
behavior: &T2,
) -> Stream<Output<T1>>
where
T1: EventOutput,
T2: EventOutput<Output = bool>,
{
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<T1, T2>(&self, label: Label, event: &T1, behavior: &T2) -> Stream<Output<T1>>
where
@ -366,6 +382,16 @@ impl Network {
self.all_with(label, t1, t2, |a, b| *a && *b)
}
pub fn is_some<T, X>(&self, label: Label, src: &T) -> Stream<bool>
where T: EventOutput<Output = Option<X>> {
self.map(label, src, |t| t.is_some())
}
pub fn is_none<T, X>(&self, label: Label, src: &T) -> Stream<bool>
where T: EventOutput<Output = Option<X>> {
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<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(OwnedAllWith7::new(label, t1, t2, t3, t4, t5, t6, t7, f))
}
/// Specialized version `all_with`.
pub fn all_with8<T1, T2, T3, T4, T5, T6, T7, T8, F, T>(
&self,
@ -4321,6 +4383,147 @@ impl<T1, T2, T3, T4, T5, T6, F> Debug for AllWith6Data<T1, T2, T3, T4, T5, T6, F
// ================
// === AllWith7 ===
// ================
pub struct AllWith7Data<T1, T2, T3, T4, T5, T6, T7, F> {
src1: watch::Ref<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 OwnedAllWith7<T1, T2, T3, T4, T5, T6, T7, F> =
stream::Node<AllWith7Data<T1, T2, T3, T4, T5, T6, T7, F>>;
pub type AllWith7<T1, T2, T3, T4, T5, T6, T7, F> =
stream::WeakNode<AllWith7Data<T1, T2, T3, T4, T5, T6, T7, F>>;
impl<T1, T2, T3, T4, T5, T6, T7, F, Out> HasOutput for AllWith7Data<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> OwnedAllWith7<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 = 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<T1, T2, T3, T4, T5, T6, T7, F, Out, T> stream::EventConsumer<T>
for OwnedAllWith7<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, _: &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<T1, T2, T3, T4, T5, T6, T7, F> Debug for AllWith7Data<T1, T2, T3, T4, T5, T6, T7, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "AllWith8Data")
}
}
// ================
// === AllWith8 ===
// ================

View File

@ -13,6 +13,10 @@ pub trait OptionOps {
where
U: Default,
F: FnOnce(Self::Item) -> U;
fn if_some_or_default<U, F>(self, f: F) -> U
where
U: Default,
F: FnOnce() -> U;
fn map_ref_or_default<U, F>(&self, f: F) -> U
where
U: Default,
@ -51,6 +55,13 @@ impl<T> OptionOps for Option<T> {
self.map_or_else(U::default, f)
}
fn if_some_or_default<U, F>(self, f: F) -> U
where
U: Default,
F: FnOnce() -> U, {
self.map_or_else(U::default, |_| f())
}
fn map_ref_or_default<U, F>(&self, f: F) -> U
where
U: Default,

View File

@ -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<D>: 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<T: Scalar + Copy> Dim<Axis2> for Vector2<T> {
fn get_dim(&self, dim: Axis2) -> Self::Dim1Type {
match dim {
Axis2::X => self.x(),
Axis2::Y => self.y(),
}
}
}
impl<T: Scalar + Copy> DimSetter<Axis2> for Vector2<T> {
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<T: Scalar + Copy> Dim<&Axis2> for Vector2<T> {
fn get_dim(&self, dim: &Axis2) -> Self::Dim1Type {
match dim {
Axis2::X => self.x(),
Axis2::Y => self.y(),
}
}
}
impl<T: Scalar + Copy> DimSetter<&Axis2> for Vector2<T> {
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
},
}
}
}
// =====================================================