Proper handling of multiple list views. (#6461)

This commit is contained in:
Wojciech Daniło 2023-05-01 18:11:05 +02:00 committed by GitHub
parent a83954571a
commit cd92d90f9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 444 additions and 159 deletions

View File

@ -77,16 +77,21 @@ use ensogl_core::display::world::*;
use ensogl_core::prelude::*; use ensogl_core::prelude::*;
use ensogl_core::control::io::mouse; use ensogl_core::control::io::mouse;
use ensogl_core::data::bounding_box::BoundingBox;
use ensogl_core::data::color;
use ensogl_core::display; use ensogl_core::display;
use ensogl_core::display::object::Event; use ensogl_core::display::object::Event;
use ensogl_core::display::object::ObjectOps; use ensogl_core::display::object::ObjectOps;
use ensogl_core::display::shape::compound::rectangle::*;
use ensogl_core::gui::cursor; use ensogl_core::gui::cursor;
use ensogl_core::gui::cursor::Cursor; use ensogl_core::gui::cursor::Cursor;
use ensogl_core::Animation; use ensogl_core::gui::cursor::Trash;
use ensogl_core::Easing; use ensogl_core::Easing;
use item::Item; use item::Item;
use placeholder::Placeholder; use placeholder::Placeholder;
use placeholder::StrongPlaceholder; use placeholder::StrongPlaceholder;
use placeholder::WeakPlaceholder;
// ============== // ==============
@ -294,6 +299,10 @@ ensogl_core::define_endpoints_2! { <T: ('static + Debug)>
/// Enable insertion points (plus icons) when moving mouse after the last list item. /// Enable insertion points (plus icons) when moving mouse after the last list item.
enable_last_insertion_point(bool), enable_last_insertion_point(bool),
/// A flag controlling this FRP debug mode. If enabled, additional logs can might be printed
/// to console.
debug(bool),
} }
Output { Output {
/// Fires whenever a new element was added to the list. /// Fires whenever a new element was added to the list.
@ -324,8 +333,10 @@ pub struct Model<T> {
cursor: Cursor, cursor: Cursor,
items: VecIndexedBy<ItemOrPlaceholder<T>, ItemOrPlaceholderIndex>, items: VecIndexedBy<ItemOrPlaceholder<T>, ItemOrPlaceholderIndex>,
root: display::object::Instance, root: display::object::Instance,
layout_with_icons: display::object::Instance,
layout: display::object::Instance, layout: display::object::Instance,
gap: f32, gap: f32,
add_elem_icon: Rectangle,
} }
impl<T> Model<T> { impl<T> Model<T> {
@ -335,10 +346,19 @@ impl<T> Model<T> {
let items = default(); let items = default();
let root = display::object::Instance::new(); let root = display::object::Instance::new();
let layout = display::object::Instance::new(); let layout = display::object::Instance::new();
let layout_with_icons = display::object::Instance::new();
let gap = default(); let gap = default();
layout_with_icons.use_auto_layout();
layout.use_auto_layout(); layout.use_auto_layout();
root.add_child(&layout); layout_with_icons.add_child(&layout);
Self { cursor, items, root, layout, gap } root.add_child(&layout_with_icons);
let add_elem_icon = Rectangle().build(|t| {
t.set_corner_radius_max()
.set_size((24.0, 24.0))
.set_color(color::Rgba::new(0.0, 0.0, 0.0, 0.2));
});
layout_with_icons.add_child(&add_elem_icon);
Self { cursor, items, root, layout, layout_with_icons, gap, add_elem_icon }
} }
} }
@ -370,11 +390,20 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
let network = self.frp.network(); let network = self.frp.network();
let model = &self.model; let model = &self.model;
let on_add_elem_icon_down = model.borrow().add_elem_icon.on_event::<mouse::Down>();
let on_down = model.borrow().layout.on_event_capturing::<mouse::Down>(); let on_down = model.borrow().layout.on_event_capturing::<mouse::Down>();
let on_up_source = scene.on_event::<mouse::Up>(); let on_up_source = scene.on_event::<mouse::Up>();
let on_move = scene.on_event::<mouse::Move>(); let on_move = scene.on_event::<mouse::Move>();
let dragged_item_network: Rc<RefCell<Option<frp::Network>>> = default();
let on_resized = model.borrow().layout.on_resized.clone_ref();
let drag_target = cursor::DragTarget::new();
frp::extend! { network frp::extend! { network
frp.private.output.request_new_item <+ on_add_elem_icon_down.map(f_!([model] {
Response::gui(model.borrow().len())
}));
target <= on_down.map(|event| event.target()); target <= on_down.map(|event| event.target());
on_up <- on_up_source.identity(); on_up <- on_up_source.identity();
@ -394,17 +423,48 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
pos_diff <- any3(&pos_diff_on_move, &pos_diff_on_down, &pos_diff_on_up); 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)); eval frp.gap((t) model.borrow_mut().set_gap(*t));
// When an item is being dragged, we are connecting to it's `on_resized` endpoint to
// watch for size changes while dragging. We want to disconnect from it as soon as the
// drag ends, and thus we are storing a local FRP network here.
dragged_item_offset <- source::<Vector2>();
dragged_item_size <- any(...);
eval_ cursor.frp.stop_drag([dragged_item_network]
*dragged_item_network.borrow_mut() = None
);
eval_ cursor.frp.start_drag ([cursor, dragged_item_size, dragged_item_offset] {
if let Some(obj) = cursor.dragged_display_object() {
let subnet = frp::Network::new("dragged_item_network");
frp::extend! { subnet
// Identity creates an explicit node in this network.
dragged_item_size <+ obj.on_resized.identity();
}
dragged_item_size.emit(obj.computed_size());
dragged_item_offset.emit(obj.position().xy());
*dragged_item_network.borrow_mut() = Some(subnet);
}
});
this_bbox <- on_resized.map(|t| BoundingBox::from_size(*t));
dragged_item_bbox <- all_with3(&dragged_item_size, &dragged_item_offset, &pos_on_move,
|size, offset, pos| BoundingBox::from_position_and_size(*pos + *offset, *size)
);
is_close <- all_with(&this_bbox, &dragged_item_bbox, |a, b| a.intersects(b)).on_change();
dragged_item_bbox_center <- dragged_item_bbox.map(|bbox| bbox.center());
cursor.frp.switch_drag_target <+ is_close.map(f!([drag_target] (t) (drag_target.clone(), *t)));
} }
self.init_add_and_remove(); self.init_add_and_remove();
let (is_dragging, drag_diff, no_drag) = let (is_dragging, _drag_diff, no_drag) =
self.init_dragging(&on_up, &on_down, &target, &pos_diff); self.init_dragging(cursor, &on_up, &on_up_cleaning_phase, &on_down, &target, &pos_diff);
let (is_trashing, trash_pointer_style) = self.init_trashing(&on_up, &drag_diff); frp::extend! { network
self.init_dropping(&on_up, &pos_on_move_down, &is_trashing); on_up_close <- on_up.gate(&is_close);
}
self.init_dropping(&on_up_close, &dragged_item_bbox_center, &is_close);
let insert_pointer_style = self.init_insertion_points(&on_up, &pos_on_move, &is_dragging); let insert_pointer_style = self.init_insertion_points(&on_up, &pos_on_move, &is_dragging);
frp::extend! { network frp::extend! { network
cursor.frp.set_style_override <+ all [insert_pointer_style, trash_pointer_style].fold(); cursor.frp.set_style_override <+ insert_pointer_style;
on_down_drag <- on_down.gate_not(&no_drag); on_down_drag <- on_down.gate_not(&no_drag);
// Do not pass events to children, as we don't know whether we are about to drag // Do not pass events to children, as we don't know whether we are about to drag
// them yet. // them yet.
@ -412,6 +472,9 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
_eval <- no_drag.on_true().map3(&on_down, &target, |_, event, target| { _eval <- no_drag.on_true().map3(&on_down, &target, |_, event, target| {
target.emit_event(event.payload.clone()); target.emit_event(event.payload.clone());
}); });
item_count_changed <- any_(&frp.on_item_added, &frp.on_item_removed);
eval_ item_count_changed (model.borrow().item_count_changed());
} }
self self
} }
@ -453,7 +516,7 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
enabled.and_option_from(|| model.item_or_placeholder_index_to_index(gap)) enabled.and_option_from(|| model.item_or_placeholder_index_to_index(gap))
}) })
}) })
); ).on_change();
index <= opt_index; index <= opt_index;
enabled <- opt_index.is_some(); enabled <- opt_index.is_some();
pointer_style <- enabled.then_constant(cursor::Style::plus()); pointer_style <- enabled.then_constant(cursor::Style::plus());
@ -491,13 +554,16 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
/// Implementation of item dragging logic. See docs of this crate to learn more. /// Implementation of item dragging logic. See docs of this crate to learn more.
fn init_dragging( fn init_dragging(
&self, &self,
cursor: &Cursor,
on_up: &frp::Stream<Event<mouse::Up>>, on_up: &frp::Stream<Event<mouse::Up>>,
on_up_cleaning_phase: &frp::Stream<Event<mouse::Up>>,
on_down: &frp::Stream<Event<mouse::Down>>, on_down: &frp::Stream<Event<mouse::Down>>,
target: &frp::Stream<display::object::Instance>, target: &frp::Stream<display::object::Instance>,
pos_diff: &frp::Stream<Vector2>, pos_diff: &frp::Stream<Vector2>,
) -> (frp::Stream<bool>, frp::Stream<Vector2>, frp::Stream<bool>) { ) -> (frp::Stream<bool>, frp::Stream<Vector2>, frp::Stream<bool>) {
let model = &self.model; let model = &self.model;
let on_up = on_up.clone_ref(); let on_up = on_up.clone_ref();
let on_up_cleaning_phase = on_up_cleaning_phase.clone_ref();
let on_down = on_down.clone_ref(); let on_down = on_down.clone_ref();
let target = target.clone_ref(); let target = target.clone_ref();
let pos_diff = pos_diff.clone_ref(); let pos_diff = pos_diff.clone_ref();
@ -517,16 +583,18 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
init_drag <- all_with(&pos_diff, init_drag_threshold, |p, t| p.y.abs() > *t).on_true(); init_drag <- all_with(&pos_diff, init_drag_threshold, |p, t| p.y.abs() > *t).on_true();
drag_disabled <- bool(&on_up, &init_no_drag).on_change(); drag_disabled <- bool(&on_up, &init_no_drag).on_change();
init_drag_not_disabled <- init_drag.gate_not(&drag_disabled); init_drag_not_disabled <- init_drag.gate_not(&drag_disabled);
is_dragging <- bool(&on_up, &init_drag_not_disabled).on_change(); is_dragging <- bool(&on_up_cleaning_phase, &init_drag_not_disabled).on_change();
drag_diff <- pos_diff.gate(&is_dragging); drag_diff <- pos_diff.gate(&is_dragging);
no_drag <- drag_disabled.gate_not(&is_dragging).on_change(); no_drag <- drag_disabled.gate_not(&is_dragging).on_change();
status <- bool(&on_up, &drag_diff).on_change(); status <- bool(&on_up_cleaning_phase, &drag_diff).on_change();
start <- status.on_true(); start <- status.on_true();
target_on_start <- target.sample(&start); target_on_start <- target.sample(&start);
let on_item_removed = &frp.private.output.on_item_removed; let on_item_removed = &frp.private.output.on_item_removed;
eval target_on_start([model, on_item_removed] (t) { eval target_on_start([model, on_item_removed, cursor] (t) {
if let Some((index, item)) = model.borrow_mut().start_item_drag(t) { let indexed_item = model.borrow_mut().start_item_drag(t);
if let Some((index, item)) = indexed_item {
cursor.start_drag(item.clone_ref());
on_item_removed.emit(Response::gui((index, Rc::new(RefCell::new(Some(item)))))); on_item_removed.emit(Response::gui((index, Rc::new(RefCell::new(Some(item))))));
} }
}); });
@ -534,44 +602,16 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
(status, drag_diff, no_drag) (status, drag_diff, no_drag)
} }
/// Implementation of item trashing logic. See docs of this crate to learn more.
fn init_trashing(
&self,
on_up: &frp::Stream<Event<mouse::Up>>,
drag_diff: &frp::Stream<Vector2>,
) -> (frp::Stream<bool>, frp::Stream<Option<cursor::Style>>) {
let on_up = on_up.clone_ref();
let drag_diff = drag_diff.clone_ref();
let model = &self.model;
let layout = model.borrow().layout.clone_ref();
let frp = &self.frp;
let network = self.frp.network();
frp::extend! { network
required_offset <- all_with(&frp.thrashing_offset_ratio, &layout.on_resized,
|ratio, size| size.y * ratio
);
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_style <- status_cleaning_phase.then_constant(cursor::Style::trash());
on <- status.on_true();
perform <- on_up.gate(&status);
eval_ on (model.collapse_all_placeholders());
eval_ perform (model.borrow_mut().trash_dragged_item());
}
(status, cursor_style)
}
/// Implementation of dropping items logic, including showing empty placeholders when the item /// Implementation of dropping items logic, including showing empty placeholders when the item
/// is dragged over a place where it could be dropped. /// is dragged over a place where it could be dropped.
fn init_dropping( fn init_dropping(
&self, &self,
on_up: &frp::Stream<Event<mouse::Up>>, on_up: &frp::Stream<Event<mouse::Up>>,
pos_on_move: &frp::Stream<Vector2>, pos_on_move: &frp::Stream<Vector2>,
is_trashing: &frp::Stream<bool>, is_close: &frp::Stream<bool>,
) { ) {
let pos_on_move = pos_on_move.clone_ref(); let pos_on_move = pos_on_move.clone_ref();
let is_trashing = is_trashing.clone_ref(); let is_close = is_close.clone_ref();
let model = &self.model; let model = &self.model;
let frp = &self.frp; let frp = &self.frp;
@ -579,20 +619,21 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
let model_borrowed = model.borrow(); let model_borrowed = model.borrow();
frp::extend! { network frp::extend! { network
on_far <- is_close.on_false();
center_points <- model_borrowed.layout.on_resized.map(f_!(model.center_points())); 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))); pos_close <- pos_on_move.sampled_gate(&is_close);
insert_index <- pos_close.map2(&center_points, f!((p, c) model.insert_index(p.x, c)));
insert_index <- insert_index.on_change(); insert_index <- insert_index.on_change();
insert_index_on_drop <- insert_index.sample(on_up).gate_not(&is_trashing); insert_index <- insert_index.sampled_gate(&is_close);
insert_index_not_trashing <- insert_index.gate_not(&is_trashing);
on_stop_trashing <- is_trashing.on_false(); eval_ on_far (model.collapse_all_placeholders());
insert_index_on_stop_trashing <- insert_index.sample(&on_stop_trashing); eval insert_index ((i) model.borrow_mut().add_insertion_point_if_type_match(*i));
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; let on_item_added = &frp.private.output.on_item_added;
insert_index_on_drop <- insert_index.sample(on_up).gate(&is_close);
eval insert_index_on_drop ([model, on_item_added] (index) eval insert_index_on_drop ([model, on_item_added] (index)
if let Some(index) = model.borrow_mut().place_dragged_item(*index) { let index = model.borrow_mut().place_dragged_item(*index);
if let Some(index) = index {
on_item_added.emit(Response::gui(index)); on_item_added.emit(Response::gui(index));
} }
); );
@ -864,7 +905,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
/// ///
/// See docs of [`Self::start_item_drag_at`] for more information. /// See docs of [`Self::start_item_drag_at`] for more information.
fn start_item_drag(&mut self, target: &display::object::Instance) -> Option<(Index, T)> { fn start_item_drag(&mut self, target: &display::object::Instance) -> Option<(Index, T)> {
let objs = target.rev_parent_chain(); let objs = target.rev_parent_chain().reversed();
let tarrget_index = objs.into_iter().find_map(|t| self.item_index_of(&t)); let tarrget_index = objs.into_iter().find_map(|t| self.item_index_of(&t));
if let Some((index, index_or_placeholder_index)) = tarrget_index { if let Some((index, index_or_placeholder_index)) = tarrget_index {
self.start_item_drag_at(index_or_placeholder_index).map(|item| (index, item)) self.start_item_drag_at(index_or_placeholder_index).map(|item| (index, item))
@ -914,10 +955,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
/// ╰─────╯ ╰╌╌╌╌╯ ╰─────╯ ╰╌╌╌╌╯ ╰─────╯ ╰─────╯ ╰╌╌╌╌╌╌╌╌╌╌╌╌◀╌╯ ╰─────╯ /// ╰─────╯ ╰╌╌╌╌╯ ╰─────╯ ╰╌╌╌╌╯ ╰─────╯ ╰─────╯ ╰╌╌╌╌╌╌╌╌╌╌╌╌◀╌╯ ╰─────╯
/// ``` /// ```
fn start_item_drag_at(&mut self, index: ItemOrPlaceholderIndex) -> Option<T> { fn start_item_drag_at(&mut self, index: ItemOrPlaceholderIndex) -> Option<T> {
self.replace_item_with_placeholder(index).map(|item| { self.replace_item_with_placeholder(index)
self.cursor.start_drag(item.clone_ref());
item
})
} }
fn replace_item_with_placeholder(&mut self, index: ItemOrPlaceholderIndex) -> Option<T> { fn replace_item_with_placeholder(&mut self, index: ItemOrPlaceholderIndex) -> Option<T> {
@ -944,7 +982,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
/// Prepare place for the dragged item by creating or reusing a placeholder and growing it to /// Prepare place for the dragged item by creating or reusing a placeholder and growing it to
/// the dragged object size. /// the dragged object size.
fn add_insertion_point(&mut self, index: ItemOrPlaceholderIndex) { fn add_insertion_point_if_type_match(&mut self, index: ItemOrPlaceholderIndex) {
if let Some(item) = if let Some(item) =
self.cursor.with_dragged_item_if_is::<T, _>(|t| t.display_object().clone()) self.cursor.with_dragged_item_if_is::<T, _>(|t| t.display_object().clone())
{ {
@ -952,13 +990,15 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
let item_size = item.computed_size().x + self.margin_at(index); let item_size = item.computed_size().x + self.margin_at(index);
let placeholder = self.get_merged_placeholder_at(index).unwrap_or_else(|| { let placeholder = self.get_merged_placeholder_at(index).unwrap_or_else(|| {
let placeholder = StrongPlaceholder::new(); let placeholder = StrongPlaceholder::new();
if index >= ItemOrPlaceholderIndex::from(self.items.len()) {
self.items.push(placeholder.clone().into());
} else {
self.items.insert(index, placeholder.clone().into()); self.items.insert(index, placeholder.clone().into());
}
placeholder placeholder
}); });
placeholder.set_target_size(item_size); placeholder.set_target_size(item_size);
self.reposition_items(); self.reposition_items();
} else {
warn!("Called function to find insertion point while no element is being dragged.")
} }
} }
@ -975,9 +1015,9 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
Item::new_from_placeholder(item.clone_ref(), placeholder).into(); Item::new_from_placeholder(item.clone_ref(), placeholder).into();
} else { } else {
// This branch should never be reached, as when dragging an item 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, // a placeholder for it (see the [`Self::add_insertion_point_if_type_match`]
// in case something breaks, we want it to still provide the user with the correct // function). However, in case something breaks, we want it to still
// outcome. // provide the user with the correct outcome.
self.items.insert(index, Item::new(item.clone_ref()).into()); self.items.insert(index, Item::new(item.clone_ref()).into());
warn!("An element was inserted without a placeholder. This should not happen."); warn!("An element was inserted without a placeholder. This should not happen.");
} }
@ -1058,6 +1098,15 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
fn insert_index(&self, x: f32, center_points: &[f32]) -> ItemOrPlaceholderIndex { fn insert_index(&self, x: f32, center_points: &[f32]) -> ItemOrPlaceholderIndex {
center_points.iter().position(|t| x < *t).unwrap_or(self.items.len()).into() center_points.iter().position(|t| x < *t).unwrap_or(self.items.len()).into()
} }
/// If the item count drops to 0, display a button to add new items.
fn item_count_changed(&self) {
if self.len() == 0 {
self.layout_with_icons.add_child(&self.add_elem_icon);
} else {
self.add_elem_icon.unset_parent();
}
}
} }
impl<T: 'static + Debug> display::Object for ListEditor<T> { impl<T: 'static + Debug> display::Object for ListEditor<T> {
@ -1065,54 +1114,3 @@ impl<T: 'static + Debug> display::Object for ListEditor<T> {
&self.root &self.root
} }
} }
// =============
// === Trash ===
// =============
mod trash {
use super::*;
ensogl_core::define_endpoints_2! {}
#[derive(Debug, CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Trash<T> {
model: Rc<TrashModel<T>>,
}
#[derive(Debug)]
pub struct TrashModel<T> {
_frp: Frp,
elem: T,
}
impl<T: display::Object + 'static> Trash<T> {
pub fn new(elem: T) -> Self {
let self_ref = Rc::new(RefCell::new(None));
let _frp = Frp::new();
let display_object = elem.display_object();
let network = &_frp.network;
let scale_animation = Animation::<f32>::new_with_init(network, 1.0);
scale_animation.simulator.update_spring(|s| s * DEBUG_ANIMATION_SPRING_FACTOR);
frp::extend! { network
eval scale_animation.value ((t) display_object.set_scale_xy(Vector2(*t,*t)));
eval_ scale_animation.on_end (self_ref.borrow_mut().take(););
}
scale_animation.target.emit(0.0);
let model = TrashModel { _frp, elem };
let model = Rc::new(model);
*self_ref.borrow_mut() = Some(model.clone());
Self { model }
}
}
impl<T: display::Object> display::Object for Trash<T> {
fn display_object(&self) -> &display::object::Instance {
self.model.elem.display_object()
}
}
}
use crate::placeholder::WeakPlaceholder;
use trash::Trash;

View File

@ -213,6 +213,9 @@ impl Model {
self.background.set_x(size.x / 2.0); self.background.set_x(size.x / 2.0);
self.track.set_x(size.x / 2.0); self.track.set_x(size.x / 2.0);
self.value.set_x(size.x / 2.0); self.value.set_x(size.x / 2.0);
self.background.set_y(size.y / 2.0);
self.track.set_y(size.y / 2.0);
self.value.set_y(size.y / 2.0);
} }
/// Set the color of the slider track or thumb. /// Set the color of the slider track or thumb.

View File

@ -55,6 +55,12 @@ impl BoundingBox {
Self::from_corners(position - size / 2.0, position + size / 2.0) Self::from_corners(position - size / 2.0, position + size / 2.0)
} }
/// Constructor of the bounding box with left bottom corner placed at the origin and the given
/// size.
pub fn from_size(size: Vector2) -> Self {
Self::from_corners(Vector2::zeros(), size)
}
/// Check whether the given `pos` lies within the bounding box. /// Check whether the given `pos` lies within the bounding box.
pub fn contains(&self, pos: Vector2) -> bool { pub fn contains(&self, pos: Vector2) -> bool {
self.contains_x(pos.x) && self.contains_y(pos.y) self.contains_x(pos.x) && self.contains_y(pos.y)

View File

@ -5,6 +5,7 @@ use crate::gui::style::*;
use crate::prelude::*; use crate::prelude::*;
use crate::application::command::FrpNetworkProvider; use crate::application::command::FrpNetworkProvider;
use crate::control::io::mouse;
use crate::data::color; use crate::data::color;
use crate::define_style; use crate::define_style;
use crate::display; use crate::display;
@ -200,6 +201,7 @@ crate::define_endpoints_2! {
Input { Input {
set_style_override (Option<Style>), set_style_override (Option<Style>),
set_style (Style), set_style (Style),
switch_drag_target((DragTarget, bool)),
} }
Output { Output {
@ -208,6 +210,8 @@ crate::define_endpoints_2! {
scene_position (Vector3), scene_position (Vector3),
/// Change between the current and the previous scene position. /// Change between the current and the previous scene position.
scene_position_delta (Vector3), scene_position_delta (Vector3),
start_drag (),
stop_drag(),
} }
} }
@ -227,12 +231,13 @@ pub struct CursorModel {
pub view: shape::View, pub view: shape::View,
pub port_selection: shape::View, pub port_selection: shape::View,
pub style: Rc<RefCell<Style>>, pub style: Rc<RefCell<Style>>,
pub dragged_item: Rc<RefCell<Option<Box<dyn Any>>>>, pub dragged_item: Rc<RefCell<Option<(Box<dyn Any>, display::object::Instance)>>>,
frp: WeakFrp,
} }
impl CursorModel { impl CursorModel {
/// Constructor. /// Constructor.
pub fn new(scene: &Scene) -> Self { pub fn new(scene: &Scene, frp: WeakFrp) -> Self {
let scene = scene.clone_ref(); let scene = scene.clone_ref();
let display_object = display::object::Instance::new(); let display_object = display::object::Instance::new();
let dragged_elem = display::object::Instance::new(); let dragged_elem = display::object::Instance::new();
@ -249,7 +254,7 @@ impl CursorModel {
port_selection_layer.add(&port_selection); port_selection_layer.add(&port_selection);
let dragged_item = default(); let dragged_item = default();
Self { scene, display_object, dragged_elem, view, port_selection, style, dragged_item } Self { scene, display_object, dragged_elem, view, port_selection, style, dragged_item, frp }
} }
fn for_each_view(&self, f: impl Fn(&shape::View)) { fn for_each_view(&self, f: impl Fn(&shape::View)) {
@ -266,10 +271,11 @@ impl CursorModel {
// ============== // ==============
/// Cursor (mouse pointer) definition. /// Cursor (mouse pointer) definition.
#[derive(Clone, CloneRef, Debug)] #[derive(Clone, CloneRef, Debug, Deref)]
#[allow(missing_docs)] #[allow(missing_docs)]
pub struct Cursor { pub struct Cursor {
pub frp: Frp, pub frp: Frp,
#[deref]
pub model: Rc<CursorModel>, pub model: Rc<CursorModel>,
} }
@ -278,7 +284,7 @@ impl Cursor {
pub fn new(scene: &Scene) -> Self { pub fn new(scene: &Scene) -> Self {
let frp = Frp::new(); let frp = Frp::new();
let network = frp.network(); let network = frp.network();
let model = CursorModel::new(scene); let model = CursorModel::new(scene, frp.downgrade());
let mouse = &scene.mouse.frp_deprecated; let mouse = &scene.mouse.frp_deprecated;
// === Animations === // === Animations ===
@ -321,7 +327,47 @@ impl Cursor {
let fade_out_spring = inactive_fade.spring() * 0.2; let fade_out_spring = inactive_fade.spring() * 0.2;
let fade_in_spring = inactive_fade.spring(); let fade_in_spring = inactive_fade.spring();
let on_up = scene.on_event::<mouse::Up>();
frp::extend! { network frp::extend! { network
// === Drag Target ===
drag_target <- any_mut::<Option<DragTarget>>();
drag_target <+ frp.switch_drag_target.map2(&drag_target,
|(target, enabled), prev| {
if let Some(prev) = prev.as_ref() {
let new_target = *enabled && target != prev;
let revoke_target = !enabled && target == prev;
if new_target {
prev.revoke.emit(());
target.grant.emit(());
Some(target.clone())
} else if revoke_target {
prev.revoke.emit(());
None
} else {
Some(target.clone())
}
} else {
target.grant.emit(());
Some(target.clone())
}
}
);
has_drag_target <- drag_target.map(|t| t.is_some()).on_change();
should_trash <- has_drag_target.map(f!([model] (has_drag_target) {
model.dragged_item.borrow().is_some() && !has_drag_target
}));
frp.set_style_override <+ should_trash.then_constant(Style::trash());
perform_trash <- on_up.gate(&should_trash);
eval_ perform_trash (model.trash_dragged_item());
// === Press / Release ===
eval press.value ((v) model.for_each_view(|vw| vw.press.set(*v))); eval press.value ((v) model.for_each_view(|vw| vw.press.set(*v)));
eval radius.value ((v) model.for_each_view(|vw| vw.radius.set(*v))); eval radius.value ((v) model.for_each_view(|vw| vw.radius.set(*v)));
eval size.value ([model] (v) { eval size.value ([model] (v) {
@ -535,16 +581,18 @@ impl Cursor {
let model = Rc::new(model); let model = Rc::new(model);
Cursor { frp, model } Cursor { frp, model }
} }
}
impl CursorModel {
/// Initialize item dragging. The provided item should implement [`display::Object`]. It will be /// Initialize item dragging. The provided item should implement [`display::Object`]. It will be
/// stored in the cursor and moved around with it. You can retrieve the item back using the /// stored in the cursor and moved around with it. You can retrieve the item back using the
/// [`Self::stop_drag`] method or another similar one. /// [`Self::stop_drag`] method or another similar one.
pub fn start_drag<T: display::Object + 'static>(&self, target: T) { pub fn start_drag<T: display::Object + 'static>(&self, target: T) {
if self.model.dragged_item.borrow().is_some() { if self.dragged_item.borrow().is_some() {
warn!("Can't start dragging an item because another item is already being dragged."); warn!("Can't start dragging an item because another item is already being dragged.");
} else { } else {
let object = target.display_object(); let object = target.display_object().clone();
self.model.dragged_elem.add_child(object); self.dragged_elem.add_child(&object);
let target_position = object.global_position().xy(); let target_position = object.global_position().xy();
let cursor_position = self.frp.scene_position.value().xy(); let cursor_position = self.frp.scene_position.value().xy();
object.set_xy(target_position - cursor_position); object.set_xy(target_position - cursor_position);
@ -552,31 +600,37 @@ impl Cursor {
let scene = scene(); let scene = scene();
let camera = scene.camera(); let camera = scene.camera();
let zoom = camera.zoom(); let zoom = camera.zoom();
self.model.dragged_elem.set_scale_xy((zoom, zoom)); self.dragged_elem.set_scale_xy((zoom, zoom));
*self.model.dragged_item.borrow_mut() = Some(Box::new(target)); *self.dragged_item.borrow_mut() = Some((Box::new(target), object));
self.frp.private.output.start_drag.emit(());
} }
} }
/// Remove the dragged item and return it as [`Any`]. If you want to retrieve the item if it is /// Remove the dragged item and return it as [`Any`]. If you want to retrieve the item if it is
/// of a particular type, use the [`Self::stop_drag_if_is`] method instead. /// of a particular type, use the [`Self::stop_drag_if_is`] method instead.
pub fn stop_drag(&self) -> Option<Box<dyn Any>> { pub fn stop_drag(&self) -> Option<Box<dyn Any>> {
self.stop_drag_if_is() let item_and_object = mem::take(&mut *self.dragged_item.borrow_mut());
if let Some((item, _)) = item_and_object {
self.stop_drag_internal();
Some(item)
} else {
warn!("Can't stop dragging an item because no item is being dragged.");
None
}
} }
/// Check whether the dragged item is of a particular type. If it is, remove and return it. /// Check whether the dragged item is of a particular type. If it is, remove and return it.
pub fn stop_drag_if_is<T: 'static>(&self) -> Option<T> { pub fn stop_drag_if_is<T: 'static>(&self) -> Option<T> {
if let Some(item) = mem::take(&mut *self.model.dragged_item.borrow_mut()) { let item_and_object = mem::take(&mut *self.dragged_item.borrow_mut());
if let Some((item, obj)) = item_and_object {
match item.downcast::<T>() { match item.downcast::<T>() {
Ok(item) => { Ok(item) => {
let elems = self.model.dragged_elem.remove_all_children(); self.stop_drag_internal();
let cursor_position = self.frp.scene_position.value().xy();
for elem in &elems {
elem.update_xy(|t| t + cursor_position);
}
Some(*item) Some(*item)
} }
Err(item) => { Err(item) => {
*self.model.dragged_item.borrow_mut() = Some(item); *self.dragged_item.borrow_mut() = Some((item, obj));
None None
} }
} }
@ -586,16 +640,39 @@ impl Cursor {
} }
} }
fn stop_drag_internal(&self) {
let elems = self.dragged_elem.remove_all_children();
let cursor_position = self.frp.scene_position.value().xy();
for elem in &elems {
elem.update_xy(|t| t + cursor_position);
}
self.frp.private.output.stop_drag.emit(());
}
/// The display object of the dragged item, if any.
pub fn dragged_display_object(&self) -> Option<display::object::Instance> {
self.dragged_item.borrow().as_ref().map(|t| t.1.clone())
}
/// Check whether the dragged item is of a particular type. /// Check whether the dragged item is of a particular type.
pub fn dragged_item_is<T: 'static>(&self) -> bool { pub fn dragged_item_is<T: 'static>(&self) -> bool {
self.model.dragged_item.borrow().as_ref().map(|item| item.is::<T>()).unwrap_or(false) self.dragged_item.borrow().as_ref().map(|item| item.0.is::<T>()).unwrap_or(false)
} }
/// Check whether the dragged item is of a particular type. If it is, call the provided function /// Check whether the dragged item is of a particular type. If it is, call the provided function
/// on it's reference. /// on it's reference.
pub fn with_dragged_item_if_is<T, Out>(&self, f: impl FnOnce(&T) -> Out) -> Option<Out> pub fn with_dragged_item_if_is<T, Out>(&self, f: impl FnOnce(&T) -> Out) -> Option<Out>
where T: 'static { where T: 'static {
self.model.dragged_item.borrow().as_ref().and_then(|item| item.downcast_ref::<T>().map(f)) self.dragged_item.borrow().as_ref().and_then(|item| item.0.downcast_ref::<T>().map(f))
}
/// Trash the dragged item.
pub fn trash_dragged_item(&self) {
let obj = self.dragged_display_object();
if let Some(obj) = obj {
self.stop_drag();
self.dragged_elem.add_child(&Trash::new(obj));
}
} }
} }
@ -604,3 +681,120 @@ impl display::Object for Cursor {
&self.model.display_object &self.model.display_object
} }
} }
// ==================
// === DragTarget ===
// ==================
/// Abstraction for display elements that can handle dragged item drop.
///
/// If a display element wants to handle dragged item, for example after the mouse hovers it, it
/// should have an instance of this struct and use the [`Cursor::switch_drag_target`] FRP endpoint
/// to notify the cursor that it wants to handle the drop. Only one drag target can be registered
/// globally at a time. If your drag target was granted the permission to handle the drop, the
/// [`Self::granted`] event will be set to `true`. In case another drag target was granted the
/// permission, your drag target's [`Self::granted`] event will turn false.
#[derive(Debug, Clone, CloneRef, Deref, Default)]
pub struct DragTarget {
model: Rc<DragTargetModel>,
}
impl DragTarget {
/// Constructor.
pub fn new() -> Self {
Self::default()
}
}
impl PartialEq for DragTarget {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.model, &other.model)
}
}
/// Internal representation of [`DragTarget`].
#[allow(missing_docs)]
#[derive(Debug)]
pub struct DragTargetModel {
network: frp::Network,
grant: frp::Source,
revoke: frp::Source,
pub granted: frp::Sampler<bool>,
}
impl DragTargetModel {
/// Constructor.
pub fn new() -> Self {
let network = frp::Network::new("DragTarget");
frp::extend! { network
grant <- source();
revoke <- source();
granted <- bool(&revoke, &grant).sampler();
}
DragTargetModel { network, grant, revoke, granted }
}
}
impl Default for DragTargetModel {
fn default() -> Self {
Self::new()
}
}
// =============
// === Trash ===
// =============
mod trash {
use super::*;
crate::define_endpoints_2! {}
/// A wrapper over any display object. After construction, the display object will be gradually
/// scaled to zero and then will be removed.
#[derive(Debug, CloneRef, Derivative)]
#[derivative(Clone(bound = ""))]
pub struct Trash<T> {
model: Rc<TrashModel<T>>,
}
/// Internal representation of [`Trash`].
#[derive(Debug)]
pub struct TrashModel<T> {
_frp: Frp,
elem: T,
}
impl<T: display::Object + 'static> Trash<T> {
/// Constructor.
pub fn new(elem: T) -> Self {
let self_ref = Rc::new(RefCell::new(None));
let _frp = Frp::new();
let display_object = elem.display_object();
let network = &_frp.network;
let scale_animation = Animation::<f32>::new_with_init(network, 1.0);
// scale_animation.simulator.update_spring(|s| s * DEBUG_ANIMATION_SPRING_FACTOR);
frp::extend! { network
eval scale_animation.value ((t) display_object.set_scale_xy(Vector2(*t,*t)));
// FIXME: does it handle detaching display object?
eval_ scale_animation.on_end (self_ref.borrow_mut().take(););
}
scale_animation.target.emit(0.0);
let model = TrashModel { _frp, elem };
let model = Rc::new(model);
*self_ref.borrow_mut() = Some(model.clone());
Self { model }
}
}
impl<T: display::Object> display::Object for Trash<T> {
fn display_object(&self) -> &display::object::Instance {
self.model.elem.display_object()
}
}
}
pub use trash::Trash;

View File

@ -25,7 +25,6 @@ use ensogl_core::prelude::*;
use enso_frp as frp; use enso_frp as frp;
use ensogl_core::application::Application; use ensogl_core::application::Application;
use ensogl_core::display;
use ensogl_core::display::navigation::navigator::Navigator; use ensogl_core::display::navigation::navigator::Navigator;
use ensogl_list_editor::ListEditor; use ensogl_list_editor::ListEditor;
use ensogl_slider as slider; use ensogl_slider as slider;
@ -48,13 +47,8 @@ pub fn main() {
run_once_initialized(run); run_once_initialized(run);
} }
fn run() { fn new_list_editor(app: &Application) -> ListEditor<slider::Slider> {
let app = Application::new("root"); let list_editor = ListEditor::new(&app.cursor);
let world = app.display.clone();
let scene = &world.default_scene;
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let vector_editor = ListEditor::new(&app.cursor);
let slider1 = app.new_view::<slider::Slider>(); let slider1 = app.new_view::<slider::Slider>();
slider1.set_size((200.0, 24.0)); slider1.set_size((200.0, 24.0));
@ -69,25 +63,38 @@ fn run() {
let network = frp.network(); let network = frp.network();
frp::extend! { network frp::extend! { network
vector_editor.insert <+ vector_editor.request_new_item.map(move |index| { list_editor.insert <+ list_editor.request_new_item.map(f!([app] (index) {
let slider = app.new_view::<slider::Slider>(); let slider = app.new_view::<slider::Slider>();
slider.set_size((200.0, 24.0)); slider.set_size((200.0, 24.0));
(**index, Rc::new(RefCell::new(Some(slider)))) (**index, Rc::new(RefCell::new(Some(slider))))
}); }));
} }
vector_editor.push(slider1); mem::forget(frp);
vector_editor.push(slider2); list_editor.push(slider1);
vector_editor.push(slider3); list_editor.push(slider2);
list_editor.push(slider3);
list_editor
}
let root = display::object::Instance::new(); fn run() {
root.set_size(Vector2(300.0, 100.0)); let app = Application::new("root");
root.add_child(&vector_editor); let world = app.display.clone();
world.add_child(&root); let scene = &world.default_scene;
let camera = scene.camera().clone_ref();
let navigator = Navigator::new(scene, &camera);
let list_editor1 = new_list_editor(&app);
list_editor1.debug(true);
world.add_child(&list_editor1);
mem::forget(list_editor1);
let list_editor2 = new_list_editor(&app);
list_editor2.set_y(50.0);
world.add_child(&list_editor2);
// list_editor2.debug(true);
mem::forget(list_editor2);
world.keep_alive_forever(); world.keep_alive_forever();
mem::forget(frp);
mem::forget(navigator); mem::forget(navigator);
mem::forget(root);
mem::forget(vector_editor);
} }

View File

@ -64,6 +64,14 @@ impl Network {
self.register(OwnedTrace::new(label, src)) self.register(OwnedTrace::new(label, src))
} }
/// Print the incoming events to console and pass them to output.
pub fn trace_if<B, T>(&self, label: Label, src: &T, gate: &B) -> Stream<Output<T>>
where
B: EventOutput<Output = bool>,
T: EventOutput, {
self.register(OwnedTraceIf::new(label, gate, src))
}
/// Profile the event resolution from this node onwards and log the result in the profiling /// Profile the event resolution from this node onwards and log the result in the profiling
/// framework. /// framework.
pub fn profile<T: EventOutput>(&self, label: Label, src: &T) -> Stream<Output<T>> { pub fn profile<T: EventOutput>(&self, label: Label, src: &T) -> Stream<Output<T>> {
@ -150,6 +158,22 @@ impl Network {
self.any(label, &value, &value2) self.any(label, &value, &value2)
} }
pub fn sampled_gate_not<T1, T2>(
&self,
label: Label,
event: &T1,
behavior: &T2,
) -> Stream<Output<T1>>
where
T1: EventOutput,
T2: EventOutput<Output = bool>,
{
let value = self.gate_not(label, event, behavior);
let on_gate_pass = self.on_false(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`. /// 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>> pub fn gate_not<T1, T2>(&self, label: Label, event: &T1, behavior: &T2) -> Stream<Output<T1>>
where where
@ -2146,6 +2170,57 @@ impl<T: EventOutput> stream::EventConsumer<Output<T>> for OwnedTrace<T> {
// ===============
// === TraceIf ===
// ===============
#[derive(Debug)]
pub struct TraceIfData<B, T> {
#[allow(dead_code)]
/// This is not accessed in this implementation but it needs to be kept so the source struct
/// stays alive at least as long as this struct.
src: T,
behavior: watch::Ref<B>,
}
pub type OwnedTraceIf<B, T> = stream::Node<TraceIfData<B, T>>;
pub type TraceIf<B, T> = stream::WeakNode<TraceIfData<B, T>>;
impl<B, T: EventOutput> HasOutput for TraceIfData<B, T> {
type Output = Output<T>;
}
impl<B: EventOutput<Output = bool>, T: EventOutput> OwnedTraceIf<B, T> {
/// Constructor.
pub fn new(label: Label, gate: &B, src1: &T) -> Self {
let src = src1.clone_ref();
let behavior = watch_stream(gate);
let def = TraceIfData { src, behavior };
Self::construct_and_connect(label, src1, def)
}
}
impl<B: EventOutput<Output = bool>, T: EventOutput> stream::EventConsumer<Output<T>>
for OwnedTraceIf<B, T>
{
fn on_event(&self, stack: CallStack, event: &Output<T>) {
if self.behavior.value() {
console_log!("[FRP] {}: {:?}", self.label(), event);
}
// warn!("[FRP] {}", stack);
self.emit_event(stack, event);
}
}
impl<B, T> stream::InputBehaviors for TraceIfData<B, T>
where B: EventOutput
{
fn input_behaviors(&self) -> Vec<Link> {
vec![Link::behavior(&self.behavior)]
}
}
// =============== // ===============
// === Profile === // === Profile ===
// =============== // ===============
@ -2879,6 +2954,8 @@ impl<Out: Data> Any<Out> {
self.upgrade().for_each(|t| t.srcs.borrow_mut().push(Box::new(src.clone_ref()))); self.upgrade().for_each(|t| t.srcs.borrow_mut().push(Box::new(src.clone_ref())));
} }
pub fn detach_all(&self) {}
/// Emit new event. It's questionable if this node type should expose the `emit` functionality, /// Emit new event. It's questionable if this node type should expose the `emit` functionality,
/// but the current usage patterns proven it is a very handy utility. This node is used to /// but the current usage patterns proven it is a very handy utility. This node is used to
/// define sources of frp output streams. Sources allow multiple streams to be attached and /// define sources of frp output streams. Sources allow multiple streams to be attached and