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