mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 02:42:28 +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::control::io::mouse;
|
||||
use ensogl_core::data::bounding_box::BoundingBox;
|
||||
use ensogl_core::data::color;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::object::Event;
|
||||
use ensogl_core::display::object::ObjectOps;
|
||||
use ensogl_core::display::shape::compound::rectangle::*;
|
||||
use ensogl_core::gui::cursor;
|
||||
use ensogl_core::gui::cursor::Cursor;
|
||||
use ensogl_core::Animation;
|
||||
use ensogl_core::gui::cursor::Trash;
|
||||
use ensogl_core::Easing;
|
||||
use item::Item;
|
||||
use placeholder::Placeholder;
|
||||
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_last_insertion_point(bool),
|
||||
|
||||
/// A flag controlling this FRP debug mode. If enabled, additional logs can might be printed
|
||||
/// to console.
|
||||
debug(bool),
|
||||
}
|
||||
Output {
|
||||
/// Fires whenever a new element was added to the list.
|
||||
@ -324,8 +333,10 @@ pub struct Model<T> {
|
||||
cursor: Cursor,
|
||||
items: VecIndexedBy<ItemOrPlaceholder<T>, ItemOrPlaceholderIndex>,
|
||||
root: display::object::Instance,
|
||||
layout_with_icons: display::object::Instance,
|
||||
layout: display::object::Instance,
|
||||
gap: f32,
|
||||
add_elem_icon: Rectangle,
|
||||
}
|
||||
|
||||
impl<T> Model<T> {
|
||||
@ -335,10 +346,19 @@ impl<T> Model<T> {
|
||||
let items = default();
|
||||
let root = display::object::Instance::new();
|
||||
let layout = display::object::Instance::new();
|
||||
let layout_with_icons = display::object::Instance::new();
|
||||
let gap = default();
|
||||
layout_with_icons.use_auto_layout();
|
||||
layout.use_auto_layout();
|
||||
root.add_child(&layout);
|
||||
Self { cursor, items, root, layout, gap }
|
||||
layout_with_icons.add_child(&layout);
|
||||
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 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_up_source = scene.on_event::<mouse::Up>();
|
||||
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.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());
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
let (is_dragging, drag_diff, no_drag) =
|
||||
self.init_dragging(&on_up, &on_down, &target, &pos_diff);
|
||||
let (is_trashing, trash_pointer_style) = self.init_trashing(&on_up, &drag_diff);
|
||||
self.init_dropping(&on_up, &pos_on_move_down, &is_trashing);
|
||||
let (is_dragging, _drag_diff, no_drag) =
|
||||
self.init_dragging(cursor, &on_up, &on_up_cleaning_phase, &on_down, &target, &pos_diff);
|
||||
frp::extend! { network
|
||||
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);
|
||||
|
||||
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);
|
||||
// Do not pass events to children, as we don't know whether we are about to drag
|
||||
// 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| {
|
||||
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
|
||||
}
|
||||
@ -453,7 +516,7 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
|
||||
enabled.and_option_from(|| model.item_or_placeholder_index_to_index(gap))
|
||||
})
|
||||
})
|
||||
);
|
||||
).on_change();
|
||||
index <= opt_index;
|
||||
enabled <- opt_index.is_some();
|
||||
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.
|
||||
fn init_dragging(
|
||||
&self,
|
||||
cursor: &Cursor,
|
||||
on_up: &frp::Stream<Event<mouse::Up>>,
|
||||
on_up_cleaning_phase: &frp::Stream<Event<mouse::Up>>,
|
||||
on_down: &frp::Stream<Event<mouse::Down>>,
|
||||
target: &frp::Stream<display::object::Instance>,
|
||||
pos_diff: &frp::Stream<Vector2>,
|
||||
) -> (frp::Stream<bool>, frp::Stream<Vector2>, frp::Stream<bool>) {
|
||||
let model = &self.model;
|
||||
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 target = target.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();
|
||||
drag_disabled <- bool(&on_up, &init_no_drag).on_change();
|
||||
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);
|
||||
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();
|
||||
target_on_start <- target.sample(&start);
|
||||
let on_item_removed = &frp.private.output.on_item_removed;
|
||||
eval target_on_start([model, on_item_removed] (t) {
|
||||
if let Some((index, item)) = model.borrow_mut().start_item_drag(t) {
|
||||
eval target_on_start([model, on_item_removed, cursor] (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))))));
|
||||
}
|
||||
});
|
||||
@ -534,44 +602,16 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
|
||||
(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
|
||||
/// is dragged over a place where it could be dropped.
|
||||
fn init_dropping(
|
||||
&self,
|
||||
on_up: &frp::Stream<Event<mouse::Up>>,
|
||||
pos_on_move: &frp::Stream<Vector2>,
|
||||
is_trashing: &frp::Stream<bool>,
|
||||
is_close: &frp::Stream<bool>,
|
||||
) {
|
||||
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 frp = &self.frp;
|
||||
@ -579,20 +619,21 @@ impl<T: display::Object + CloneRef + Debug> ListEditor<T> {
|
||||
let model_borrowed = model.borrow();
|
||||
|
||||
frp::extend! { network
|
||||
on_far <- is_close.on_false();
|
||||
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_on_drop <- insert_index.sample(on_up).gate_not(&is_trashing);
|
||||
insert_index_not_trashing <- insert_index.gate_not(&is_trashing);
|
||||
insert_index <- insert_index.sampled_gate(&is_close);
|
||||
|
||||
on_stop_trashing <- is_trashing.on_false();
|
||||
insert_index_on_stop_trashing <- insert_index.sample(&on_stop_trashing);
|
||||
update_insert_index <- any(&insert_index_not_trashing, &insert_index_on_stop_trashing);
|
||||
eval update_insert_index ((i) model.borrow_mut().add_insertion_point(*i));
|
||||
eval_ on_far (model.collapse_all_placeholders());
|
||||
eval insert_index ((i) model.borrow_mut().add_insertion_point_if_type_match(*i));
|
||||
|
||||
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)
|
||||
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));
|
||||
}
|
||||
);
|
||||
@ -864,7 +905,7 @@ impl<T: display::Object + CloneRef + 'static> Model<T> {
|
||||
///
|
||||
/// See docs of [`Self::start_item_drag_at`] for more information.
|
||||
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));
|
||||
if let Some((index, index_or_placeholder_index)) = tarrget_index {
|
||||
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> {
|
||||
self.replace_item_with_placeholder(index).map(|item| {
|
||||
self.cursor.start_drag(item.clone_ref());
|
||||
item
|
||||
})
|
||||
self.replace_item_with_placeholder(index)
|
||||
}
|
||||
|
||||
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
|
||||
/// 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) =
|
||||
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 placeholder = self.get_merged_placeholder_at(index).unwrap_or_else(|| {
|
||||
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());
|
||||
}
|
||||
placeholder
|
||||
});
|
||||
placeholder.set_target_size(item_size);
|
||||
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();
|
||||
} else {
|
||||
// This branch should never be reached, as when dragging an item we always create
|
||||
// a placeholder for it (see the [`Self::add_insertion_point`] function). However,
|
||||
// in case something breaks, we want it to still provide the user with the correct
|
||||
// outcome.
|
||||
// a placeholder for it (see the [`Self::add_insertion_point_if_type_match`]
|
||||
// function). However, in case something breaks, we want it to still
|
||||
// provide the user with the correct outcome.
|
||||
self.items.insert(index, Item::new(item.clone_ref()).into());
|
||||
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 {
|
||||
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> {
|
||||
@ -1065,54 +1114,3 @@ impl<T: 'static + Debug> display::Object for ListEditor<T> {
|
||||
&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.track.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.
|
||||
|
@ -55,6 +55,12 @@ impl BoundingBox {
|
||||
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.
|
||||
pub fn contains(&self, pos: Vector2) -> bool {
|
||||
self.contains_x(pos.x) && self.contains_y(pos.y)
|
||||
|
@ -5,6 +5,7 @@ use crate::gui::style::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::application::command::FrpNetworkProvider;
|
||||
use crate::control::io::mouse;
|
||||
use crate::data::color;
|
||||
use crate::define_style;
|
||||
use crate::display;
|
||||
@ -200,6 +201,7 @@ crate::define_endpoints_2! {
|
||||
Input {
|
||||
set_style_override (Option<Style>),
|
||||
set_style (Style),
|
||||
switch_drag_target((DragTarget, bool)),
|
||||
}
|
||||
|
||||
Output {
|
||||
@ -208,6 +210,8 @@ crate::define_endpoints_2! {
|
||||
scene_position (Vector3),
|
||||
/// Change between the current and the previous scene position.
|
||||
scene_position_delta (Vector3),
|
||||
start_drag (),
|
||||
stop_drag(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,12 +231,13 @@ pub struct CursorModel {
|
||||
pub view: shape::View,
|
||||
pub port_selection: shape::View,
|
||||
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 {
|
||||
/// Constructor.
|
||||
pub fn new(scene: &Scene) -> Self {
|
||||
pub fn new(scene: &Scene, frp: WeakFrp) -> Self {
|
||||
let scene = scene.clone_ref();
|
||||
let display_object = display::object::Instance::new();
|
||||
let dragged_elem = display::object::Instance::new();
|
||||
@ -249,7 +254,7 @@ impl CursorModel {
|
||||
port_selection_layer.add(&port_selection);
|
||||
|
||||
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)) {
|
||||
@ -266,10 +271,11 @@ impl CursorModel {
|
||||
// ==============
|
||||
|
||||
/// Cursor (mouse pointer) definition.
|
||||
#[derive(Clone, CloneRef, Debug)]
|
||||
#[derive(Clone, CloneRef, Debug, Deref)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct Cursor {
|
||||
pub frp: Frp,
|
||||
#[deref]
|
||||
pub model: Rc<CursorModel>,
|
||||
}
|
||||
|
||||
@ -278,7 +284,7 @@ impl Cursor {
|
||||
pub fn new(scene: &Scene) -> Self {
|
||||
let frp = Frp::new();
|
||||
let network = frp.network();
|
||||
let model = CursorModel::new(scene);
|
||||
let model = CursorModel::new(scene, frp.downgrade());
|
||||
let mouse = &scene.mouse.frp_deprecated;
|
||||
|
||||
// === Animations ===
|
||||
@ -321,7 +327,47 @@ impl Cursor {
|
||||
let fade_out_spring = inactive_fade.spring() * 0.2;
|
||||
let fade_in_spring = inactive_fade.spring();
|
||||
|
||||
let on_up = scene.on_event::<mouse::Up>();
|
||||
|
||||
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 radius.value ((v) model.for_each_view(|vw| vw.radius.set(*v)));
|
||||
eval size.value ([model] (v) {
|
||||
@ -535,16 +581,18 @@ impl Cursor {
|
||||
let model = Rc::new(model);
|
||||
Cursor { frp, model }
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorModel {
|
||||
/// 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
|
||||
/// [`Self::stop_drag`] method or another similar one.
|
||||
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.");
|
||||
} else {
|
||||
let object = target.display_object();
|
||||
self.model.dragged_elem.add_child(object);
|
||||
let object = target.display_object().clone();
|
||||
self.dragged_elem.add_child(&object);
|
||||
let target_position = object.global_position().xy();
|
||||
let cursor_position = self.frp.scene_position.value().xy();
|
||||
object.set_xy(target_position - cursor_position);
|
||||
@ -552,31 +600,37 @@ impl Cursor {
|
||||
let scene = scene();
|
||||
let camera = scene.camera();
|
||||
let zoom = camera.zoom();
|
||||
self.model.dragged_elem.set_scale_xy((zoom, zoom));
|
||||
*self.model.dragged_item.borrow_mut() = Some(Box::new(target));
|
||||
self.dragged_elem.set_scale_xy((zoom, zoom));
|
||||
*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
|
||||
/// of a particular type, use the [`Self::stop_drag_if_is`] method instead.
|
||||
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.
|
||||
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>() {
|
||||
Ok(item) => {
|
||||
let elems = self.model.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.stop_drag_internal();
|
||||
Some(*item)
|
||||
}
|
||||
Err(item) => {
|
||||
*self.model.dragged_item.borrow_mut() = Some(item);
|
||||
*self.dragged_item.borrow_mut() = Some((item, obj));
|
||||
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.
|
||||
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
|
||||
/// on it's reference.
|
||||
pub fn with_dragged_item_if_is<T, Out>(&self, f: impl FnOnce(&T) -> Out) -> Option<Out>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === 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 ensogl_core::application::Application;
|
||||
use ensogl_core::display;
|
||||
use ensogl_core::display::navigation::navigator::Navigator;
|
||||
use ensogl_list_editor::ListEditor;
|
||||
use ensogl_slider as slider;
|
||||
@ -48,13 +47,8 @@ pub fn main() {
|
||||
run_once_initialized(run);
|
||||
}
|
||||
|
||||
fn run() {
|
||||
let app = Application::new("root");
|
||||
let world = app.display.clone();
|
||||
let scene = &world.default_scene;
|
||||
let camera = scene.camera().clone_ref();
|
||||
let navigator = Navigator::new(scene, &camera);
|
||||
let vector_editor = ListEditor::new(&app.cursor);
|
||||
fn new_list_editor(app: &Application) -> ListEditor<slider::Slider> {
|
||||
let list_editor = ListEditor::new(&app.cursor);
|
||||
|
||||
let slider1 = app.new_view::<slider::Slider>();
|
||||
slider1.set_size((200.0, 24.0));
|
||||
@ -69,25 +63,38 @@ fn run() {
|
||||
let network = frp.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>();
|
||||
slider.set_size((200.0, 24.0));
|
||||
(**index, Rc::new(RefCell::new(Some(slider))))
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
vector_editor.push(slider1);
|
||||
vector_editor.push(slider2);
|
||||
vector_editor.push(slider3);
|
||||
mem::forget(frp);
|
||||
list_editor.push(slider1);
|
||||
list_editor.push(slider2);
|
||||
list_editor.push(slider3);
|
||||
list_editor
|
||||
}
|
||||
|
||||
let root = display::object::Instance::new();
|
||||
root.set_size(Vector2(300.0, 100.0));
|
||||
root.add_child(&vector_editor);
|
||||
world.add_child(&root);
|
||||
fn run() {
|
||||
let app = Application::new("root");
|
||||
let world = app.display.clone();
|
||||
let scene = &world.default_scene;
|
||||
let camera = scene.camera().clone_ref();
|
||||
let navigator = Navigator::new(scene, &camera);
|
||||
|
||||
let 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();
|
||||
mem::forget(frp);
|
||||
mem::forget(navigator);
|
||||
mem::forget(root);
|
||||
mem::forget(vector_editor);
|
||||
}
|
||||
|
@ -64,6 +64,14 @@ impl Network {
|
||||
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
|
||||
/// framework.
|
||||
pub fn profile<T: EventOutput>(&self, label: Label, src: &T) -> Stream<Output<T>> {
|
||||
@ -150,6 +158,22 @@ impl Network {
|
||||
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`.
|
||||
pub fn gate_not<T1, T2>(&self, label: Label, event: &T1, behavior: &T2) -> Stream<Output<T1>>
|
||||
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 ===
|
||||
// ===============
|
||||
@ -2879,6 +2954,8 @@ impl<Out: Data> Any<Out> {
|
||||
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,
|
||||
/// 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
|
||||
|
Loading…
Reference in New Issue
Block a user