mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 15:51:54 +03:00
Proper handling of multiple list views. (#6461)
This commit is contained in:
parent
a83954571a
commit
cd92d90f9f
@ -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(¢er_points, f!((p, c) model.insert_index(p.x, c)));
|
pos_close <- pos_on_move.sampled_gate(&is_close);
|
||||||
|
insert_index <- pos_close.map2(¢er_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;
|
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user