From a0191bbbb0bda67a21ca247e358033796caa1cc6 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Mon, 26 Apr 2021 09:14:49 +0200 Subject: [PATCH 01/25] feat: Implement number and range pickers. --- src/rust/ensogl/example/src/lib.rs | 3 +- src/rust/ensogl/example/src/slider.rs | 94 ++++++ .../ensogl/lib/components/src/component.rs | 91 ++++++ src/rust/ensogl/lib/components/src/lib.rs | 7 +- .../ensogl/lib/components/src/selector.rs | 37 +++ .../lib/components/src/selector/common.rs | 126 ++++++++ .../src/selector/common/base_frp.rs | 117 ++++++++ .../components/src/selector/common/model.rs | 270 ++++++++++++++++++ .../components/src/selector/common/shape.rs | 174 +++++++++++ .../lib/components/src/selector/number.rs | 108 +++++++ .../lib/components/src/selector/range.rs | 116 ++++++++ src/rust/ensogl/lib/components/src/shadow.rs | 43 ++- .../ensogl/lib/core/src/application/frp.rs | 4 + .../lib/core/src/data/color/space/def.rs | 10 + .../display/shape/primitive/style_watch.rs | 12 + src/rust/ensogl/lib/theme/src/lib.rs | 13 + src/rust/lib/frp/src/nodes.rs | 60 ++++ 17 files changed, 1278 insertions(+), 7 deletions(-) create mode 100644 src/rust/ensogl/example/src/slider.rs create mode 100644 src/rust/ensogl/lib/components/src/component.rs create mode 100644 src/rust/ensogl/lib/components/src/selector.rs create mode 100644 src/rust/ensogl/lib/components/src/selector/common.rs create mode 100644 src/rust/ensogl/lib/components/src/selector/common/base_frp.rs create mode 100644 src/rust/ensogl/lib/components/src/selector/common/model.rs create mode 100644 src/rust/ensogl/lib/components/src/selector/common/shape.rs create mode 100644 src/rust/ensogl/lib/components/src/selector/number.rs create mode 100644 src/rust/ensogl/lib/components/src/selector/range.rs diff --git a/src/rust/ensogl/example/src/lib.rs b/src/rust/ensogl/example/src/lib.rs index a6aca0cc1b..0260b1cd7f 100644 --- a/src/rust/ensogl/example/src/lib.rs +++ b/src/rust/ensogl/example/src/lib.rs @@ -29,12 +29,13 @@ #[allow(clippy::option_map_unit_fn)] pub mod animation; +pub mod complex_shape_system; pub mod dom_symbols; pub mod easing_animator; pub mod glyph_system; pub mod list_view; pub mod shape_system; -pub mod complex_shape_system; +pub mod slider; pub mod sprite_system; pub mod sprite_system_benchmark; pub mod text_area; diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs new file mode 100644 index 0000000000..407db035c5 --- /dev/null +++ b/src/rust/ensogl/example/src/slider.rs @@ -0,0 +1,94 @@ +//! A debug scene which shows the number and range selector. + +use crate::prelude::*; +use wasm_bindgen::prelude::*; + +use ensogl_core::application::Application; +use ensogl_core::display::object::ObjectOps; +use ensogl_core::system::web; +use ensogl_gui_components::selector; +use ensogl_text_msdf_sys::run_once_initialized; +use ensogl_theme as theme; + + + +// =================== +// === Entry Point === +// =================== + +/// An entry point. +#[wasm_bindgen] +#[allow(dead_code)] +pub fn entry_point_slider() { + web::forward_panic_hook_to_console(); + web::set_stdout(); + web::set_stack_trace_limit(); + run_once_initialized(|| { + let app = Application::new(&web::get_html_element_by_id("root").unwrap()); + init(&app); + mem::forget(app); + }); +} + +fn make_number_picker(app:&Application) -> NeverDrop { + let slider = app.new_view::(); + slider.frp.resize(Vector2(200.0,50.0)); + app.display.add_child(&slider); + NeverDrop::new(slider) +} + +fn make_range_picker(app:&Application) -> NeverDrop { + let slider = app.new_view::(); + slider.frp.resize(Vector2(400.0,50.0)); + app.display.add_child(&slider); + NeverDrop::new(slider) +} + +// Utility struct that leaks a struct instead of dropping it. Will cause memory leaks. +struct NeverDrop { + value: Option +} + +impl NeverDrop { + fn new(value:T) -> Self { + NeverDrop{value:Some(value)} + } + fn inner(&self) -> &T { + self.value.as_ref().unwrap() + } +} + +impl Drop for NeverDrop { + fn drop(&mut self) { + std::mem::forget(self.value.take()); + } +} + + + +// ======================== +// === Init Application === +// ======================== + +fn init(app:&Application) { + theme::builtin::dark::register(&app); + theme::builtin::light::register(&app); + theme::builtin::light::enable(&app); + + let slider1 = make_number_picker(app); + slider1.inner().frp.allow_click_selection(true); + let slider2 = make_number_picker(app); + slider2.inner().frp.resize(Vector2(400.0,50.0)); + slider2.inner().frp.set_bounds.emit((-100.0,100.0)); + slider2.inner().set_position_y(50.0); + slider2.inner().frp.use_overflow_bounds((-150.0,200.0)); + slider2.inner().frp.set_caption(Some("Value:".to_string())); + + let slider3 = make_range_picker(app); + slider3.inner().set_position_y(-100.0); + + let slider4 = make_range_picker(app); + slider4.inner().set_position_y(-200.0); + slider4.inner().frp.use_overflow_bounds((-2.0,3.0)); + slider4.inner().frp.set_caption(Some("Caption".to_string())); +} diff --git a/src/rust/ensogl/lib/components/src/component.rs b/src/rust/ensogl/lib/components/src/component.rs new file mode 100644 index 0000000000..e9572ec7df --- /dev/null +++ b/src/rust/ensogl/lib/components/src/component.rs @@ -0,0 +1,91 @@ +//! UI component consisting of an FRP and a Model. +//! +//! Enforces correct ownership of components: Model must not own Frp. Both need to be owned via +//! `Rc` by the parent struct, which itself acts as a smart-pointer to the FRP. +//! +//! Requires both the Frp component, and the Model to implement a trait each, which provide +//! functionality for constructing / initialising the components. + +use crate::prelude::*; + +use enso_frp as frp; +use ensogl_core::application::Application; +use ensogl_core::application::command::CommandApi; +use ensogl_core::application; +use ensogl_core::display::shape::*; +use ensogl_core::display; + + + +// ============= +// === Model === +// ============= + +/// Model that can be used in a Component. Requires a constructor that takes an application and +/// returns `Self`. The model will be created with this constructor when constructing the +/// `Component`. +pub trait Model { + /// Constructor. + fn new(app:&Application) -> Self; +} + + + +// =========== +// === FRP === +// =========== + +/// Frp that can be used in a Component. The FRP requires an initializer that will be called during +/// the construction of the component. `Default` + `CommandApi` are usually implemented when using +/// the `ensogl_core::define_endpoints!` macro to create an FRP API. +pub trait Frp : Default + CommandApi { + /// Frp initializer. + fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp); +} + + + +// ================= +// === Component === +// ================= + +/// Base struct for UI components in EnsoGL. Contains the Data/Shape model and the FPR exposing its +/// behaviour. +#[derive(Clone,CloneRef,Debug)] +pub struct Component { + /// Public FRP api of the Component. + pub frp : Rc, + pub(crate) model : Rc, + /// Reference to the application the Component belongs to. Generally required for implementing + /// `application::View` and initialising the `Mode`l and `Frp` and thus provided by the + /// `Component`. + pub app : Application, +} + +impl> Component { + /// Constructor. + pub fn new(app:&Application) -> Self { + let app = app.clone_ref(); + let model = Rc::new(M::new(&app)); + let frp = F::default(); + let style = StyleWatchFrp::new(&app.display.scene().style_sheet); + frp.init(&app, &model,&style); + let frp = Rc::new(frp); + Self { model, frp, app } + } +} + +impl display::Object for Component { + fn display_object(&self) -> &display::object::Instance { + &self.model.display_object() + } +} + +impl> Deref for Component { + type Target = F; + fn deref(&self) -> &Self::Target { &self.frp } +} + +impl application::command::FrpNetworkProvider for Component { + fn network(&self) -> &frp::Network { self.frp.network() } +} diff --git a/src/rust/ensogl/lib/components/src/lib.rs b/src/rust/ensogl/lib/components/src/lib.rs index 8cd1693907..486bf6d039 100644 --- a/src/rust/ensogl/lib/components/src/lib.rs +++ b/src/rust/ensogl/lib/components/src/lib.rs @@ -4,6 +4,7 @@ #![feature(option_result_contains)] #![feature(trait_alias)] +#![feature(clamp)] #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] @@ -16,11 +17,13 @@ #![recursion_limit="512"] +pub mod component; pub mod drop_down_menu; -pub mod list_view; pub mod label; -pub mod toggle_button; +pub mod list_view; pub mod shadow; +pub mod selector; +pub mod toggle_button; /// Commonly used types and functions. pub mod prelude { diff --git a/src/rust/ensogl/lib/components/src/selector.rs b/src/rust/ensogl/lib/components/src/selector.rs new file mode 100644 index 0000000000..0251f7e1c9 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector.rs @@ -0,0 +1,37 @@ +//! UI components for selecting numbers, and a range of numbers. +mod range; +mod number; +mod common; + +use ensogl_core::application; +use ensogl_core::application::Application; + + + +// ==================== +// === Number Picker === +// ==================== + +/// UI component for selecting a number. +pub type NumberPicker = crate::component::Component; + +impl application::View for NumberPicker { + fn label() -> &'static str { "NumberPicker" } + fn new(app:&Application) -> Self { NumberPicker::new(app) } + fn app(&self) -> &Application { &self.app } +} + + + +// ========================= +// === Number Range Picker === +// ========================= + +/// UI component for selecting a number. +pub type NumberRangePicker = crate::component::Component; + +impl application::View for NumberRangePicker { + fn label() -> &'static str { "RangePicker" } + fn new(app:&Application) -> Self { NumberRangePicker::new(app) } + fn app(&self) -> &Application { &self.app } +} diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/common.rs new file mode 100644 index 0000000000..d7657182d4 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector/common.rs @@ -0,0 +1,126 @@ +//! Common functionality for both the Number and Range selector. +use crate::prelude::*; + +use enso_frp as frp; +use enso_frp::Network; +use ensogl_core::frp::io::Mouse; +use ensogl_core::gui::component::ShapeViewEvents; + +pub mod base_frp; +pub mod model; +pub mod shape; + +pub use base_frp::*; +pub use model::*; + + + +// ============== +// === Bounds === +// ============== + +/// Bounds of a selection. This indicates the lowest and highest value that can be selected in a +/// selection component. +pub type Bounds = (f32, f32); + +/// Frp utility method to normalise the given value to the given Bounds. +pub fn normalise_value((value,range):&(f32,Bounds)) -> f32 { + (value - range.0) / (range.1 - range.0) +} + +/// Frp utility method to compute the absolute value from a normalised value. +/// Inverse of `normalise_value`. +pub fn absolute_value((range,normalised_value):&(Bounds,f32)) -> f32 { + ((normalised_value * (range.1 - range.0)) + range.0) +} + +/// Returns the normalised value that correspond to the click posiiton on the shape. +/// For use in FRP `map` method, thus taking references. +#[allow(clippy::trivially_copy_pass_by_ref)] +pub fn position_to_normalised_value(pos:&Vector2,width:&f32) -> f32 { + ((pos.x / (width/2.0)) + 1.0) / 2.0 +} + +/// Check whether the given value is within the given bounds. End points are exclusive. +fn value_in_bounds(value:f32, bounds:Bounds) -> bool { + value > bounds.0 && value < bounds.1 +} + +/// Check whether the given bounds are completely contained in the second bounds. +pub fn range_in_bounds(range:Bounds, bounds:Bounds) -> bool { + value_in_bounds(range.0, bounds) && value_in_bounds(range.1, bounds) +} + +/// Clamp `value` to the `overflow_bounds`, or to [0, 1] if no bounds are given. +/// For use in FRP `map` method, thus taking references. +#[allow(clippy::trivially_copy_pass_by_ref)] +pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option) -> f32 { + if let Some(overflow_bounds) = overflow_bounds{ + value.clamp(overflow_bounds.0,overflow_bounds.1) + } else { + value.clamp(0.0, 1.0) + } +} + +/// Indicates whether the `value` would be clamped when given to `clamp_with_overflow`. +/// For use in FRP `map` method, thus taking references. +#[allow(clippy::trivially_copy_pass_by_ref)] +pub fn should_clamp_with_overflow(range:&Bounds, overflow_bounds:&Option) -> bool { + if let Some(overflow_bounds) = overflow_bounds { + range_in_bounds(*range,*overflow_bounds) + } else { + range_in_bounds(*range,(0.0,1.0)) + } +} + + + +// ============= +// === Range === +// ============= + +pub type Range = (f32, f32); + +/// Return the range with sorted components. +pub fn sorted_range(range:Range) -> Range { + if range.0 > range.1 { + (range.1, range.0) + } else { + range + } +} + + + +// ======================= +// === Shape Utilities === +// ======================= + + +/// Return whether a dragging action has been started from the given shape. +/// A dragging action is started by a mouse down on a shape, followed by a movement of the mouse. +/// It is ended by a mouse up. +pub fn shape_is_dragged(network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { + frp::extend! { network + mouse_up <- mouse.up.constant(()); + mouse_down <- mouse.down.constant(()); + over_shape <- bool(&shape.mouse_out,&shape.mouse_over); + mouse_down_over_shape <- mouse_down.gate(&over_shape); + is_dragging_shape <- bool(&mouse_up,&mouse_down_over_shape); + } + is_dragging_shape +} + +/// Returns the position of a mouse down on a shape. The position is given relative to the origin +/// of the shape position. +pub fn relative_shape_click_position(model:&Model, network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { + let model = model.clone_ref(); + frp::extend! { network + mouse_down <- mouse.down.constant(()); + over_shape <- bool(&shape.mouse_out,&shape.mouse_over); + mouse_down_over_shape <- mouse_down.gate(&over_shape); + background_click_positon <- mouse.position.sample(&mouse_down_over_shape); + background_click_positon <- background_click_positon.map(move |pos| pos - model.position().xy()); + } + background_click_positon +} diff --git a/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs b/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs new file mode 100644 index 0000000000..71ee8011d1 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs @@ -0,0 +1,117 @@ +//! Frp logic common to both Number and Range selector. + +use crate::prelude::*; + +use enso_frp as frp; +use enso_frp::io::Mouse; +use enso_frp::Network; +use ensogl_core::display::object::ObjectOps; +use ensogl_core::display::shape::StyleWatchFrp; +use ensogl_theme as theme; + +use crate::selector::common; +use crate::selector::common::Model; +use crate::shadow; + + + +// =========================== +// === Frp utility methods === +// =========================== + +/// Compute the slider width from the given shape size. For use in FRP, thus taking a reference. +#[allow(clippy::trivially_copy_pass_by_ref)] +fn slider_area_width(size:&Vector2) -> f32 { + // Radius of the rounded corners of the background shape. + let rounded_width = size.y / 2.0; + size.x - rounded_width +} + + + +// =============== +// === BaseFrp === +// =============== + +/// Frp endpoints provided for general information about mouse interactions and shape properties +/// of the `common::Model`. +pub struct BaseFrp { + /// Current maximum extent of the track in scene coordinate space. + pub track_max_width : frp::Stream, + /// Indicates whether there is an ongoing dragging action from the left overflow shape. + pub is_dragging_left_overflow : frp::Stream, + /// Indicates whether there is an ongoing dragging action from the right overflow shape. + pub is_dragging_right_overflow : frp::Stream, + /// Indicates whether there is an ongoing dragging action from the track shape. + pub is_dragging_track : frp::Stream, + /// Indicates whether there is an ongoing dragging action from the background shape. + pub is_dragging_background : frp::Stream, + /// Indicates whether there is an ongoing dragging action from the invisible shape that covers + /// the left end of the track. + pub is_dragging_left_handle : frp::Stream, + /// Indicates whether there is an ongoing dragging action from the invisible shape that covers + /// the right end of the track. + pub is_dragging_right_handle : frp::Stream, + /// Indicates whether there is an ongoing dragging action on any of the component shapes. + pub is_dragging_any : frp::Stream, +} + +impl BaseFrp { + pub fn new(model:&Model, style:&StyleWatchFrp, network:&Network, size:frp::Stream, mouse:&Mouse) -> BaseFrp { + let shadow = shadow::frp_from_style(style,theme::shadow); + let text_size = style.get_number(theme::text::size); + + let is_dragging_left_overflow = common::shape_is_dragged(network,&model.left_overflow.events,mouse); + let is_dragging_right_overflow = common::shape_is_dragged(network,&model.right_overflow.events,mouse); + let is_dragging_track = common::shape_is_dragged(network,&model.track.events, mouse); + let is_dragging_background = common::shape_is_dragged(network,&model.background.events,mouse); + let is_dragging_left_handle = common::shape_is_dragged(network,&model.track_handle_left.events,mouse); + let is_dragging_right_handle = common::shape_is_dragged(network,&model.track_handle_right.events,mouse); + + // Initialisation of components. Required for correct layout on startup. + model.label_right.set_position_y(text_size.value()/2.0); + model.label_left.set_position_y(text_size.value()/2.0); + model.label.set_position_y(text_size.value()/2.0); + model.caption_left.set_position_y(text_size.value()/2.0); + model.caption_center.set_position_y(text_size.value()/2.0); + + frp::extend! { network + + // Style updates. + shadow_padding <- shadow.size.map(|&v| Vector2(v,v)); + eval text_size ((size) { + model.label.set_position_y(size / 2.0); + model.label_right.set_position_y(size / 2.0); + model.label_left.set_position_y(size / 2.0); + }); + + // Caption updates. + update_caption_position <- all(&size,&text_size); + eval update_caption_position((args) model.update_caption_position(args)); + eval model.caption_center.frp.width((width) + model.caption_center.set_position_x(-width/2.0) + ); + + // Size updates + track_max_width <- size.map(slider_area_width); + size_update <- all(size,shadow_padding); + eval size_update(((size, shadow_padding)) { + model.set_size(*size,*shadow_padding) + }); + + // Mouse IO + is_dragging_overflow <- any(&is_dragging_left_overflow,&is_dragging_right_overflow); + is_dragging_handle <- any(&is_dragging_left_handle,&is_dragging_right_handle); + is_dragging_any <- any4( + &is_dragging_track, + &is_dragging_background, + &is_dragging_overflow, + &is_dragging_handle, + ); + } + + BaseFrp {track_max_width,is_dragging_left_overflow,is_dragging_right_overflow, + is_dragging_track,is_dragging_background,is_dragging_left_handle, + is_dragging_right_handle,is_dragging_any} + } +} diff --git a/src/rust/ensogl/lib/components/src/selector/common/model.rs b/src/rust/ensogl/lib/components/src/selector/common/model.rs new file mode 100644 index 0000000000..1038e49381 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector/common/model.rs @@ -0,0 +1,270 @@ +//! Base model for the number and range selector components. +use crate::prelude::*; + +use crate::component; +use crate::selector::common; + +use enso_frp as frp; +use ensogl_core::application::Application; +use ensogl_core::application; +use ensogl_core::display::shape::*; +use ensogl_core::display; +use ensogl_text as text; + +use super::shape::*; + +const LABEL_OFFSET : f32 = 13.0; + + +// ============================================== +// === Utilities - Decimal Aligned Text Field === +// ============================================== + +mod decimal_aligned { + use super::*; + + ensogl_core::define_endpoints! { + Input { + set_content(f32), + } + Output {} + } + + impl component::Frp for Frp { + fn init(&self, app: &Application, model: &Model, _style: &StyleWatchFrp) { + let frp = &self; + let network = &frp.network; + let _scene = app.display.scene(); + + frp::extend! { network + formatted <- frp.set_content.map(|value| format!("{:.2}", value)); + // FIXME: the next line is locale dependent. We need a way to get the current locale + // dependent decimal separator for this. + left <- formatted.map(|s| s.split('.').next().map(|s| s.to_string())).unwrap(); + + model.label_left.set_content <+ left; + model.label.set_content <+ formatted; + + eval model.label_left.width((offset) model.label.set_position_x(-offset-LABEL_OFFSET)); + } + } + } + + #[derive(Clone,CloneRef,Debug)] + pub struct Model { + root : display::object::Instance, + label : text::Area, + label_left : text::Area, + label_right: text::Area, + } + + impl component::Model for Model { + fn new(app:&Application) -> Self { + let logger = Logger::new("DecimalAlignedLabel"); + let root = display::object::Instance::new(&logger); + let label = app.new_view::(); + let label_left = app.new_view::(); + let label_right = app.new_view::(); + + label.remove_from_scene_layer_DEPRECATED(&app.display.scene().layers.main); + label.add_to_scene_layer_DEPRECATED(&app.display.scene().layers.label); + + root.add_child(&label); + root.add_child(&label_left); + root.add_child(&label_right); + + Self{root,label,label_left,label_right} + } + } + + impl display::Object for Model { + fn display_object(&self) -> &display::object::Instance { self.label.display_object() } + } + + pub type FloatLabel = crate::component::Component; + + impl application::View for FloatLabel { + fn label() -> &'static str { "DecimalAlignedLabel" } + fn new(app:&Application) -> Self { FloatLabel::new(app) } + fn app(&self) -> &Application { &self.app } + } +} + + + +// ============= +// === Model === +// ============= + +#[derive(Clone,CloneRef,Debug)] +pub struct Model { + pub background : background::View, + pub track : track::View, + pub track_handle_left : io_rect::View, + pub track_handle_right : io_rect::View, + pub left_overflow : left_overflow::View, + pub right_overflow : right_overflow::View, + pub label : decimal_aligned::FloatLabel, + pub label_left : text::Area, + pub label_right : text::Area, + pub caption_left : text::Area, + pub caption_center : text::Area, + pub root : display::object::Instance, +} + +impl component::Model for Model { + fn new(app: &Application) -> Self { + let logger = Logger::new("selector::common::Model"); + let root = display::object::Instance::new(&logger); + let label = app.new_view::(); + let label_left = app.new_view::(); + let label_right = app.new_view::(); + let caption_center = app.new_view::(); + let caption_left = app.new_view::(); + let background = background::View::new(&logger); + let track = track::View::new(&logger); + let track_handle_left = io_rect::View::new(&logger); + let track_handle_right = io_rect::View::new(&logger); + let left_overflow = left_overflow::View::new(&logger); + let right_overflow = right_overflow::View::new(&logger); + + let app = app.clone_ref(); + let scene = app.display.scene(); + scene.layers.add_shapes_order_dependency::(); + scene.layers.add_shapes_order_dependency::(); + scene.layers.add_shapes_order_dependency::(); + scene.layers.add_shapes_order_dependency::(); + + root.add_child(&label); + root.add_child(&label_left); + root.add_child(&label_right); + root.add_child(&caption_left); + root.add_child(&caption_center); + root.add_child(&background); + root.add_child(&track); + root.add_child(&right_overflow); + + label_left.remove_from_scene_layer_DEPRECATED(&scene.layers.main); + label_left.add_to_scene_layer_DEPRECATED(&scene.layers.label); + label_right.remove_from_scene_layer_DEPRECATED(&scene.layers.main); + label_right.add_to_scene_layer_DEPRECATED(&scene.layers.label); + caption_left.remove_from_scene_layer_DEPRECATED(&scene.layers.main); + caption_left.add_to_scene_layer_DEPRECATED(&scene.layers.label); + caption_center.remove_from_scene_layer_DEPRECATED(&scene.layers.main); + caption_center.add_to_scene_layer_DEPRECATED(&scene.layers.label); + + Self{root,label,background,track,left_overflow,right_overflow,caption_left,caption_center, + label_left,label_right,track_handle_left,track_handle_right} + } +} + +impl Model { + pub fn set_size(&self, size:Vector2, shadow_padding:Vector2) { + let padded_size = size+shadow_padding; + self.background.size.set(padded_size); + self.track.size.set(padded_size); + self.left_overflow.size.set(padded_size); + self.right_overflow.size.set(padded_size); + + let left_padding = LABEL_OFFSET; + let overflow_icon_size = size.y; + let label_offset = size.x / 2.0 - overflow_icon_size + left_padding; + + self.label_left.set_position_x(-label_offset); + self.label_right.set_position_x(label_offset-self.label_right.width.value()); + + let overflow_icon_offset = size.x / 2.0 - overflow_icon_size / 2.0; + self.left_overflow.set_position_x(-overflow_icon_offset); + self.right_overflow.set_position_x(overflow_icon_offset); + + let track_handle_size = Vector2::new(size.y/2.0,size.y); + self.track_handle_left.size.set(track_handle_size); + self.track_handle_right.size.set(track_handle_size); + } + + pub fn update_caption_position(&self, (size,text_size):&(Vector2,f32)) { + let left_padding = LABEL_OFFSET; + let overflow_icon_size = size.y / 2.0; + let caption_offset = size.x / 2.0 - overflow_icon_size - left_padding; + self.caption_left.set_position_x(-caption_offset); + self.caption_left.set_position_y(text_size / 2.0); + self.caption_center.set_position_y(text_size / 2.0); + } + + pub fn use_track_handles(&self, value:bool) { + if value { + self.track.add_child(&self.track_handle_left); + self.track.add_child(&self.track_handle_right); + } else { + self.track.remove_child(&self.track_handle_left); + self.track.remove_child(&self.track_handle_right); + } + } + + pub fn set_background_value(&self, value:f32) { + self.track.left.set(0.0); + self.track.right.set(value); + } + + pub fn set_background_range(&self, value:common::Range, size:Vector2) { + self.track.left.set(value.0); + self.track.right.set(value.1); + + self.track_handle_left.set_position_x(value.0 * size.x - size.x / 2.0); + self.track_handle_right.set_position_x(value.1 * size.x - size.x / 2.0); + } + + pub fn set_center_label_content(&self, value:f32) { + self.label.frp.set_content.emit(value) + } + + pub fn set_left_label_content(&self, value:f32) { + self.label_left.frp.set_content.emit(format!("{:.2}", value)) + } + + pub fn set_right_label_content(&self, value:f32) { + self.label_right.frp.set_content.emit(format!("{:.2}", value)) + } + + pub fn set_caption_left(&self, caption:Option) { + let caption = caption.unwrap_or_default(); + self.caption_left.frp.set_content.emit(caption); + } + + pub fn set_caption_center(&self, caption:Option) { + let caption = caption.unwrap_or_default(); + self.caption_center.frp.set_content.emit(caption); + } + + pub fn show_left_overflow(&self, value:bool) { + if value { + self.root.add_child(&self.left_overflow); + } else { + self.root.remove_child(&self.left_overflow); + } + } + + pub fn show_right_overflow(&self, value:bool) { + if value { + self.root.add_child(&self.right_overflow); + } else { + self.root.remove_child(&self.right_overflow); + } + } + + pub fn left_corner_round(&self,value:bool) { + let corner_roundness = if value { 1.0 } else { 0.0 }; + self.background.corner_left.set(corner_roundness); + self.track.corner_left.set(corner_roundness) + } + + pub fn right_corner_round(&self,value:bool) { + let corner_roundness = if value { 1.0 } else { 0.0 }; + self.background.corner_right.set(corner_roundness); + self.track.corner_right.set(corner_roundness) + } +} + +impl display::Object for Model { + fn display_object(&self) -> &display::object::Instance { &self.root } +} diff --git a/src/rust/ensogl/lib/components/src/selector/common/shape.rs b/src/rust/ensogl/lib/components/src/selector/common/shape.rs new file mode 100644 index 0000000000..6b9fc218a4 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector/common/shape.rs @@ -0,0 +1,174 @@ +use crate::prelude::*; + +use ensogl_core::display::shape::*; +use ensogl_theme as theme; + +use crate::shadow; + + + +// ================== +// === Background === +// ================== + +/// Utility struct that contains the background shape for the selector components, as well as some +/// meta information about it. This information can be used to align other shapes with the +/// background. +#[allow(dead_code)] +struct Background { + width : Var, + height : Var, + corner_radius : Var, + shape : AnyShape, +} + +impl Background { + fn new(corner_left:&Var,corner_right:&Var,style:&StyleWatch) -> Background { + let sprite_width : Var = "input_size.x".into(); + let sprite_height : Var = "input_size.y".into(); + let width = &sprite_width - shadow::size(style).px(); + let height = &sprite_height - shadow::size(style).px(); + let corner_radius = &height/2.0; + let rect_left = Rect((&width/2.0,&height)).corners_radius(&corner_radius*corner_left); + let rect_left = rect_left.translate_x(-&width/4.0); + let rect_right = Rect((&width/2.0,&height)).corners_radius(&corner_radius*corner_right); + let rect_right = rect_right.translate_x(&width/4.0); + let rect_center = Rect((&corner_radius,&height)); + + let shape = (rect_left+rect_right+rect_center).into(); + + Background{width,height,corner_radius,shape} + } +} + +/// Background shape. Appears as a rect with rounded corners. The roundness of each corner can be +/// toggled by passing `0.0` or `1.0` to either `corner_left` and `corner_right` where `0.0` means +/// "not rounded" and `1.0` means "rounded". +pub mod background { + use super::*; + + ensogl_core::define_shape_system! { + (style:Style,corner_left:f32,corner_right:f32) { + let background = Background::new(&corner_left,&corner_right,style); + let color = style.get_color(theme::component::slider::background); + let shadow = shadow::from_shape(background.shape.clone(),style); + let background = background.shape.fill(color); + (shadow + background).into() + } + } +} + + + +// =============== +// === IO Rect === +// =============== + +/// Utility shape that is invisible but provides mouse input. Fills the whole sprite. +pub mod io_rect { + use super::*; + + ensogl_core::define_shape_system! { + () { + let sprite_width : Var = "input_size.x".into(); + let sprite_height : Var = "input_size.y".into(); + let rect = Rect((&sprite_width,&sprite_height)); + let shape = rect.fill(HOVER_COLOR); + shape.into() + } + } +} + + + +// ============= +// === Track === +// ============= + +/// Track of the selector. Appears as filled area of the background. Has a definable start and +/// end-point (`left`, `right`) which are passed as normalised values relative to the maximum +/// width. For consistency with the background shape, also has the property to round either side +/// of the track, when required to fit the background shape. (See `Background` above.). +pub mod track { + use super::*; + + ensogl_core::define_shape_system! { + (style:Style,left:f32,right:f32,corner_left:f32,corner_right:f32) { + let background = Background::new(&corner_left,&corner_right,style); + let width = background.width; + let height = background.height; + + let track_width = (&right - &left) * &width; + let track_start = left * &width; + let track = Rect((&track_width,&height)); + let track = track.translate_x(&track_start + (track_width / 2.0) ); + let track = track.translate_x(-width/2.0); + let track = track.intersection(&background.shape); + + let track_color = style.get_color(theme::component::slider::track::color); + let track = track.fill(track_color); + track.into() + } + } +} + + + +// ================ +// === OverFlow === +// ================ + +/// Utility struct that contains the overflow shape, and some metadata that can be used to place and +/// align it. +#[allow(dead_code)] +struct OverFlowShape { + width : Var, + height : Var, + shape : AnyShape +} + +impl OverFlowShape { + fn new(style:&StyleWatch) -> Self { + let sprite_width : Var = "input_size.x".into(); + let sprite_height : Var = "input_size.y".into(); + let width = &sprite_width - shadow::size(style).px(); + let height = &sprite_height - shadow::size(style).px(); + let overflow_color = style.get_color(theme::component::slider::overflow::color); + let shape = Triangle(&sprite_height/6.0,&sprite_height/6.0); + let shape = shape.fill(&overflow_color); + + let hover_area = Circle(&height); + let hover_area = hover_area.fill(HOVER_COLOR); + + let shape = (shape + hover_area).into(); + OverFlowShape{shape,width,height} + } +} + +/// Overflow shape that indicates a value can not be shown. Appears as a triangle/arrow pointing +/// left. +pub mod left_overflow { + use super::*; + + ensogl_core::define_shape_system! { + (style:Style) { + let overflow_shape = OverFlowShape::new(style); + let shape = overflow_shape.shape.rotate(-90.0_f32.to_radians().radians()); + shape.into() + } + } +} + +/// Overflow shape that indicates a value can not be shown. Appears as a triangle/arrow pointing +/// right. +pub mod right_overflow { + use super::*; + + ensogl_core::define_shape_system! { + (style:Style) { + let overflow_shape = OverFlowShape::new(style); + let shape = overflow_shape.shape.rotate(90.0_f32.to_radians().radians()); + shape.into() + } + } +} diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs new file mode 100644 index 0000000000..2d0de2d0ae --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -0,0 +1,108 @@ +use crate::prelude::*; + +use enso_frp as frp; +use ensogl_core::application::Application; +use ensogl_core::display::shape::*; +use ensogl_core::display::shape::StyleWatchFrp; + +use crate::component; +use crate::selector::common::Model; +use crate::selector::common::base_frp::BaseFrp; +use crate::selector::common::relative_shape_click_position; + +use super::common::shape::*; +use super::common::*; + + + +// =========== +// === FRP === +// =========== + +ensogl_core::define_endpoints! { + Input { + set_value(f32), + resize(Vector2), + set_bounds(Bounds), + use_overflow_bounds(Option), + allow_click_selection(bool), + set_caption(Option), + set_left_corner_round(bool), + set_right_corner_round(bool), + } + Output { + value(f32), + bounds(Bounds), + } +} + +impl component::Frp for Frp { + fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp){ + let frp = &self; + let network = &frp.network; + let scene = app.display.scene(); + let mouse = &scene.mouse.frp; + + let base_frp = BaseFrp::new(model,style,network,frp.resize.clone().into(),mouse); + + let track_shape_system = scene.shapes.shape_system(PhantomData::); + track_shape_system.shape_system.set_pointer_events(false); + + let background_click = relative_shape_click_position(model, network, &model.background.events, mouse); + let track_click = relative_shape_click_position(model, network, &model.track.events, mouse); + + frp::extend! { network + + // Rebind + frp.source.bounds <+ frp.set_bounds; + frp.source.value <+ frp.set_value; + + // Simple Inputs + eval frp.set_caption((caption) model.set_caption_left(caption.clone())); + eval frp.set_left_corner_round ((value) model.left_corner_round(*value)); + eval frp.set_right_corner_round((value) model.right_corner_round(*value)); + + // Intern State Vales + norm_value <- all2(&frp.value,&frp.bounds).map(normalise_value); + _has_overflow_bounds <- frp.use_overflow_bounds.map(|value| value.is_none()); + normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map(|(overflow_bounds,bounds)| + overflow_bounds.map(|(lower,upper)| (normalise_value(&(lower,*bounds)),normalise_value(&(upper,*bounds))) ) + ); + has_underflow <- norm_value.map(|value| *value < 0.0); + has_overflow <- norm_value.map(|value| *value > 1.0); + + // Value Updates + eval norm_value((value) model.set_background_value(*value)); + eval has_underflow ((underflow) model.show_left_overflow(*underflow)); + eval has_overflow ((overflow) model.show_right_overflow(*overflow)); + eval frp.value((value) model.set_center_label_content(*value)); + + // Mouse IO + click <- any(&track_click,&background_click).gate(&frp.allow_click_selection); + click_value_update <- click.map2(&base_frp.track_max_width,position_to_normalised_value); + + is_dragging <- any( + base_frp.is_dragging_track, + base_frp.is_dragging_background, + base_frp.is_dragging_left_overflow, + base_frp.is_dragging_right_overflow + ); + + drag_movement <- mouse.translation.gate(&is_dragging); + delta_value <- drag_movement.map2(&base_frp.track_max_width, |delta,width| (delta.x + delta.y) / width); + + drag_value_update <- delta_value.map2(&norm_value,|delta,value|*delta+*value); + value_update <- any(&click_value_update,&drag_value_update); + clamped_update <- value_update.map2(&normalised_overflow_bounds,clamp_with_overflow); + frp.source.value <+ all(&frp.set_bounds,&clamped_update).map(absolute_value); + } + + // Init defaults. + frp.set_bounds((0.0,1.0)); + frp.allow_click_selection(false); + frp.use_overflow_bounds(None); + frp.set_value(0.5); + frp.set_left_corner_round(true); + frp.set_right_corner_round(true); + } +} diff --git a/src/rust/ensogl/lib/components/src/selector/range.rs b/src/rust/ensogl/lib/components/src/selector/range.rs new file mode 100644 index 0000000000..39d4d9dd90 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector/range.rs @@ -0,0 +1,116 @@ +use crate::prelude::*; + +use enso_frp as frp; +use ensogl_core::Animation; +use ensogl_core::application::Application; +use ensogl_core::display::shape::*; +use ensogl_core::display::shape::StyleWatchFrp; + +use crate::selector::common::Model; +use crate::component; +use crate::selector::common::base_frp::BaseFrp; + +use super::common::*; +use super::common::Range; + + + +// =========== +// === Frp === +// =========== + +ensogl_core::define_endpoints! { + Input { + set_value(Range), + resize(Vector2), + set_bounds(Bounds), + use_overflow_bounds(Option), + set_caption(Option), + set_left_corner_round(bool), + set_right_corner_round(bool), + } + Output { + value(Range), + bounds(Bounds), + } +} + +impl component::Frp for Frp { + fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp){ + let frp = &self; + let network = &frp.network; + let scene = app.display.scene(); + let mouse = &scene.mouse.frp; + + let _slider_animation = Animation::::new(network); + + let base_frp = BaseFrp::new(model,style,network,frp.resize.clone().into(),mouse); + + model.use_track_handles(true); + + frp::extend! { network + + // Rebind + frp.source.bounds <+ frp.set_bounds; + frp.source.value <+ frp.set_value; + + // Simple Inputs + eval frp.set_caption((caption) model.set_caption_center(caption.clone())); + eval frp.set_left_corner_round ((value) model.left_corner_round(*value)); + eval frp.set_right_corner_round((value) model.right_corner_round(*value)); + + // Intern State Vales + norm_value <- all2(&frp.value,&frp.bounds).map(|((lower,upper),bounds)|{ + (normalise_value(&(*lower,*bounds)),normalise_value(&(*upper,*bounds))) + }); + _has_overflow_bounds <- frp.use_overflow_bounds.map(|value| value.is_none()); + normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map(|(overflow_bounds,bounds)| + overflow_bounds.map(|(lower,upper)| (normalise_value(&(lower,*bounds)),normalise_value(&(upper,*bounds))) ) + ); + has_underflow <- norm_value.map(|value| (value.0.min(value.1)) < 0.0); + has_overflow <- norm_value.map(|value| (value.0.max(value.1)) > 1.0); + + // Slider Updates + update_slider <- all(&norm_value,&frp.resize); + eval update_slider(((value,size)) model.set_background_range(*value,*size)); + eval has_underflow ((underflow) model.show_left_overflow(*underflow)); + eval has_overflow ((overflow) model.show_right_overflow(*overflow)); + eval frp.value((value) { + model.set_left_label_content(value.0); + model.set_right_label_content(value.1); + }); + + // Mouse IO + let change_left_and_right_value = base_frp.is_dragging_track.clone_ref(); + change_left_value <- base_frp.is_dragging_left_handle || base_frp.is_dragging_left_overflow; + change_right_value <- base_frp.is_dragging_right_handle || base_frp.is_dragging_right_overflow; + + drag_movement <- mouse.translation.gate(&base_frp.is_dragging_any); + drag_delta <- drag_movement.map2(&base_frp.track_max_width, |delta,width| (delta.x + delta.y) / width); + + drag_center_delta <- drag_delta.gate(&change_left_and_right_value); + center_update <- drag_center_delta.map2(&norm_value,|delta,(l,u)| (l+delta,u+delta)); + + drag_left_delta <- drag_delta.gate(&change_left_value); + left_update <- drag_left_delta.map2(&norm_value,|delta,(l,u)| (l+delta,*u)); + + drag_right_delta <- drag_delta.gate(&change_right_value); + right_update <- drag_right_delta.map2(&norm_value,|delta,(l,u)| (*l,u+delta)); + + any_update <- any3(¢er_update,&left_update,&right_update); + is_in_bounds <- any_update.map2(&normalised_overflow_bounds, |range,bounds| should_clamp_with_overflow(range,bounds)); + new_value_absolute <- all(&frp.set_bounds,&any_update).map(|(bounds,(lower,upper))|{ + sorted_range((absolute_value(&(*bounds,*lower)),absolute_value(&(*bounds,*upper)))) + }); + frp.source.value <+ new_value_absolute.gate(&is_in_bounds); + } + + // Init defaults; + frp.set_bounds((0.0,1.0)); + frp.use_overflow_bounds(None); + frp.set_value((0.25,0.75)); + frp.set_left_corner_round(true); + frp.set_right_corner_round(true); + + } +} diff --git a/src/rust/ensogl/lib/components/src/shadow.rs b/src/rust/ensogl/lib/components/src/shadow.rs index 653bbb296f..5bac6d549b 100644 --- a/src/rust/ensogl/lib/components/src/shadow.rs +++ b/src/rust/ensogl/lib/components/src/shadow.rs @@ -3,14 +3,15 @@ use crate::prelude::*; use ensogl_core::data::color; use ensogl_core::display::DomSymbol; -use ensogl_core::display::style; use ensogl_core::display::shape::*; use ensogl_core::display::shape::AnyShape; +use ensogl_core::display::style; +use ensogl_core::frp; use ensogl_core::system::web::StyleSetter; use ensogl_theme as theme; -/// Defines the apearance of a shadow +/// Defines the appearance of a shadow #[derive(Debug,Copy,Clone)] pub struct Parameters { base_color : color::Rgba, @@ -37,13 +38,19 @@ pub fn parameters_from_style_path(style:&StyleWatch,path:impl Into) } } -/// Return a shadow for the given shape. Exact appearance will depends on the theme parameters. +/// Utility method to retrieve the size of the shadow. can be used to determine shape padding etc. +pub fn size(style:&StyleWatch) -> f32 { + let parameters = parameters_from_style_path(style,theme::shadow); + parameters.size +} + +/// Return a shadow for the given shape. Exact appearance will depend on the theme parameters. pub fn from_shape(base_shape:AnyShape, style:&StyleWatch) -> AnyShape { let alpha = Var::::from(1.0); from_shape_with_alpha(base_shape,&alpha,style) } -/// Return a shadow for the given shape. Exact appearance will depends on the theme parameters. +/// Return a shadow for the given shape. Exact appearance will depend on the theme parameters. /// The color will be multiplied with the given alpha value, which is useful for fade-in/out /// animations. pub fn from_shape_with_alpha(base_shape:AnyShape,alpha:&Var,style:&StyleWatch) -> AnyShape { @@ -90,3 +97,31 @@ pub fn add_to_dom_element(element:&DomSymbol, style:&StyleWatch,logger:&Logger) let shadow = format!("{}px {}px {}px {}px rgba(0,0,0,{})",off_x,off_y,blur,spread,alpha); element.dom().set_style_or_warn("box-shadow",shadow,&logger); } + + +/// Provides FRP endpoints for the parameters that define the appearance of a shadow +#[derive(Debug,Clone,CloneRef)] +#[allow(missing_docs)] +pub struct ParametersFrp { + pub base_color : frp::Sampler, + pub fading : frp::Sampler, + pub size : frp::Sampler, + pub spread : frp::Sampler, + pub exponent : frp::Sampler, + pub offset_x : frp::Sampler, + pub offset_y : frp::Sampler +} + +/// Return FRP endpoints for the parameters that define a shadow. +pub fn frp_from_style(style:&StyleWatchFrp,path:impl Into) -> ParametersFrp { + let path: style::Path = path.into(); + ParametersFrp{ + base_color: style.get_color(&path), + fading: style.get_color(&path.sub("fading")), + size: style.get_number(&path.sub("size")), + spread: style.get_number(&path.sub("spread")), + exponent: style.get_number(&path.sub("exponent")), + offset_x: style.get_number(&path.sub("offset_x")), + offset_y: style.get_number(&path.sub("offset_y")) + } +} diff --git a/src/rust/ensogl/lib/core/src/application/frp.rs b/src/rust/ensogl/lib/core/src/application/frp.rs index 4e156af98b..3e4f5181df 100644 --- a/src/rust/ensogl/lib/core/src/application/frp.rs +++ b/src/rust/ensogl/lib/core/src/application/frp.rs @@ -379,6 +379,10 @@ macro_rules! define_endpoints { self.status_map.clone() } } + + impl $crate::application::command::FrpNetworkProvider for Frp { + fn network(&self) -> &$crate::frp::Network { &self.network } + } }; } diff --git a/src/rust/ensogl/lib/core/src/data/color/space/def.rs b/src/rust/ensogl/lib/core/src/data/color/space/def.rs index 253e87ea5c..f15e510c14 100644 --- a/src/rust/ensogl/lib/core/src/data/color/space/def.rs +++ b/src/rust/ensogl/lib/core/src/data/color/space/def.rs @@ -392,6 +392,16 @@ impl Rgb { } impl Rgba { + /// Constructor. + pub fn black() -> Self { + Self::new(0.0,0.0,0.0,1.0) + } + + /// Constructor. + pub fn white() -> Self { + Self::new(1.0,1.0,1.0,1.0) + } + /// Constructor. pub fn red() -> Self { Self::new(1.0,0.0,0.0,1.0) diff --git a/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs b/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs index 400dc1074e..21fb549d8f 100644 --- a/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs +++ b/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs @@ -82,6 +82,18 @@ impl StyleWatchFrp { sampler } + /// Queries style sheet value for a number. Returns 0.0 if not found. + pub fn get_number(&self, path:impl Into) -> frp::Sampler { + let network = &self.network; + let (source,current) = self.get_internal(path); + frp::extend! { network + value <- source.map(|t| t.number().unwrap_or_else(|| 0.0)); + sampler <- value.sampler(); + } + source.emit(current); + sampler + } + /// Queries style sheet color, if not found fallbacks to [`FALLBACK_COLOR`]. pub fn get_color>(&self, path:T) -> frp::Sampler { let network = &self.network; diff --git a/src/rust/ensogl/lib/theme/src/lib.rs b/src/rust/ensogl/lib/theme/src/lib.rs index 0d34377d02..798b3922d3 100644 --- a/src/rust/ensogl/lib/theme/src/lib.rs +++ b/src/rust/ensogl/lib/theme/src/lib.rs @@ -346,6 +346,19 @@ define_themes! { [light:0, dark:1] padding_inner_y = 2.0, 2.0; height = 30.0, 30.0; } + slider { + background = graph_editor::node::background , graph_editor::node::background; + handle { + color = Lcha(0.3,0.0,0.0,1.0), Lcha(0.7,0.0,0.0,1.0); + } + track { + color = Lcha(0.9,0.0,0.0,0.5), Lcha(0.1,0.0,0.0,0.5); + } + overflow { + color = Lcha(0.0,0.0,0.0,1.0), Lcha(1.0,0.0,0.0,1.0); + scale = 1.0, 1.0; + } + } } diff --git a/src/rust/lib/frp/src/nodes.rs b/src/rust/lib/frp/src/nodes.rs index 78c2857d15..a0cd6dd700 100644 --- a/src/rust/lib/frp/src/nodes.rs +++ b/src/rust/lib/frp/src/nodes.rs @@ -241,6 +241,17 @@ impl Network { self.register(OwnedAny::new4(label,t1,t2,t3,t4)) } + /// Specialized version of `any`. + pub fn any5 + (&self, label:Label, t1:&T1, t2:&T2, t3:&T3, t4:&T4, t5:&T5) -> Stream + where T1:EventOutput, + T2:EventOutput, + T3:EventOutput, + T4:EventOutput, + T5:EventOutput { + self.register(OwnedAny::new5(label,t1,t2,t3,t4,t5)) + } + // === Any_ === @@ -274,6 +285,13 @@ impl Network { self.register(OwnedAny_::new4(label,t1,t2,t3,t4)) } + /// Specialized version of `any_`. + pub fn any5_ + (&self, label:Label, t1:&T1, t2:&T2, t3:&T3, t4:&T4, t5:&T5) -> Stream<()> + where T1:EventOutput, T2:EventOutput, T3:EventOutput, T4:EventOutput, T5:EventOutput { + self.register(OwnedAny_::new5(label,t1,t2,t3,t4,t5)) + } + // === All === @@ -608,6 +626,16 @@ impl DynamicNetwork { OwnedAny::new4(label,t1,t2,t3,t4).into() } + pub fn any5 + (self, label:Label, t1:&T1, t2:&T2, t3:&T3, t4:&T4, t5:&T5) -> OwnedStream + where T1:EventOutput, + T2:EventOutput, + T3:EventOutput, + T4:EventOutput, + T5:EventOutput { + OwnedAny::new5(label,t1,t2,t3,t4,t5).into() + } + // === Any_ === @@ -636,6 +664,12 @@ impl DynamicNetwork { OwnedAny_::new4(label,t1,t2,t3,t4).into() } + pub fn any5_ + (self, label:Label, t1:&T1, t2:&T2, t3:&T3, t4:&T4, t5:&T5) -> OwnedStream<()> + where T1:EventOutput, T2:EventOutput, T3:EventOutput, T4:EventOutput, T5:EventOutput { + OwnedAny_::new5(label,t1,t2,t3,t4,t5).into() + } + // === All === @@ -1227,6 +1261,16 @@ impl OwnedAny { Self::new(label).with(t1).with(t2).with(t3).with(t4) } + /// Constructor for 5 input streams. + pub fn new5(label:Label, t1:&T1, t2:&T2, t3:&T3, t4:&T4, t5:&T5) -> Self + where T1:EventOutput, + T2:EventOutput, + T3:EventOutput, + T4:EventOutput, + T5:EventOutput { + Self::new(label).with(t1).with(t2).with(t3).with(t4).with(t5) + } + /// 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 @@ -1321,6 +1365,12 @@ impl OwnedAny_ { where T1:EventOutput, T2:EventOutput, T3:EventOutput, T4:EventOutput { Self::new(label).with(t1).with(t2).with(t3).with(t4) } + + /// Constructor for 5 input streams. + pub fn new5(label:Label, t1:&T1, t2:&T2, t3:&T3, t4:&T4, t5:&T5) -> Self + where T1:EventOutput, T2:EventOutput, T3:EventOutput, T4:EventOutput, T5:EventOutput { + Self::new(label).with(t1).with(t2).with(t3).with(t4).with(t5) + } } impl Any_ { @@ -1615,6 +1665,16 @@ impl OwnedAllMut { T4:EventOutput { Self::new(label).with(t1).with(t2).with(t3).with(t4) } + + /// Constructor for 5 input streams. + pub fn new5(label:Label, t1:&T1, t2:&T2, t3:&T3, t4:&T4, t5:&T5) -> Self + where T1:EventOutput, + T2:EventOutput, + T3:EventOutput, + T4:EventOutput, + T5:EventOutput { + Self::new(label).with(t1).with(t2).with(t3).with(t4).with(t5) + } } impl AllMut { From 2305feb453c823290fc46ae62fc0a4119eb97ea1 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Mon, 26 Apr 2021 09:30:24 +0200 Subject: [PATCH 02/25] chore: Update changelog. --- CHANGELOG.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e1efdcb75..7647e1ffa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,14 @@ big, and it's cheaper to send the precomputed bins rather than the entire dataset. +#### EnsoGL (rendering engine) + +- [Unified shadow generation][1411]. Added a toolset to create shadows for + arbitrary UI components. +- [Components for picking numbers and ranges.][1524]. We now have some internal + re-usable UI components for selecting numbers or a range. Stay tuned for them + appearing in the IDE. +
![Bug Fixes](/docs/assets/tags/bug_fixes.svg) #### Visual Environment @@ -130,11 +138,6 @@ - [Improved the performance of the graph editor, particularly when opening a project for the first time.][1445] -#### EnsoGL (rendering engine) - -- [Unified shadow generation][1411]. Added a toolset to create shadows for - arbitrary UI components. - #### Enso Compiler If you're interested in the enhancements and fixes made to the Enso compiler, @@ -175,6 +178,7 @@ you can find their release notes [1438]: https://github.com/enso-org/ide/pull/1438 [1367]: https://github.com/enso-org/ide/pull/1367 [1445]: https://github.com/enso-org/ide/pull/1445 +[1524]: https://github.com/enso-org/ide/pull/1524
From 6f9b5adf544bcda9fdce8beb451103e27852840e Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Wed, 28 Apr 2021 09:15:16 +0200 Subject: [PATCH 03/25] Apply style suggestions from code review Co-authored-by: Adam Obuchowicz --- src/rust/ensogl/lib/components/src/selector.rs | 8 ++++---- .../ensogl/lib/components/src/selector/common.rs | 2 +- .../lib/components/src/selector/common/model.rs | 3 ++- .../lib/components/src/selector/common/shape.rs | 2 +- .../ensogl/lib/components/src/selector/number.rs | 12 ++++++------ 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector.rs b/src/rust/ensogl/lib/components/src/selector.rs index 0251f7e1c9..07dc2d4e46 100644 --- a/src/rust/ensogl/lib/components/src/selector.rs +++ b/src/rust/ensogl/lib/components/src/selector.rs @@ -8,9 +8,9 @@ use ensogl_core::application::Application; -// ==================== +// ===================== // === Number Picker === -// ==================== +// ===================== /// UI component for selecting a number. pub type NumberPicker = crate::component::Component; @@ -23,9 +23,9 @@ impl application::View for NumberPicker { -// ========================= +// =========================== // === Number Range Picker === -// ========================= +// =========================== /// UI component for selecting a number. pub type NumberRangePicker = crate::component::Component; diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/common.rs index d7657182d4..c5dd9670ed 100644 --- a/src/rust/ensogl/lib/components/src/selector/common.rs +++ b/src/rust/ensogl/lib/components/src/selector/common.rs @@ -48,7 +48,7 @@ fn value_in_bounds(value:f32, bounds:Bounds) -> bool { /// Check whether the given bounds are completely contained in the second bounds. pub fn range_in_bounds(range:Bounds, bounds:Bounds) -> bool { - value_in_bounds(range.0, bounds) && value_in_bounds(range.1, bounds) + value_in_bounds(range.0,bounds) && value_in_bounds(range.1,bounds) } /// Clamp `value` to the `overflow_bounds`, or to [0, 1] if no bounds are given. diff --git a/src/rust/ensogl/lib/components/src/selector/common/model.rs b/src/rust/ensogl/lib/components/src/selector/common/model.rs index 1038e49381..4cb439b863 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/model.rs @@ -16,6 +16,7 @@ use super::shape::*; const LABEL_OFFSET : f32 = 13.0; + // ============================================== // === Utilities - Decimal Aligned Text Field === // ============================================== @@ -160,7 +161,7 @@ impl component::Model for Model { impl Model { pub fn set_size(&self, size:Vector2, shadow_padding:Vector2) { - let padded_size = size+shadow_padding; + let padded_size = size + shadow_padding; self.background.size.set(padded_size); self.track.size.set(padded_size); self.left_overflow.size.set(padded_size); diff --git a/src/rust/ensogl/lib/components/src/selector/common/shape.rs b/src/rust/ensogl/lib/components/src/selector/common/shape.rs index 6b9fc218a4..f39eff40e9 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/shape.rs @@ -23,7 +23,7 @@ struct Background { } impl Background { - fn new(corner_left:&Var,corner_right:&Var,style:&StyleWatch) -> Background { + fn new(corner_left:&Var, corner_right:&Var, style:&StyleWatch) -> Background { let sprite_width : Var = "input_size.x".into(); let sprite_height : Var = "input_size.y".into(); let width = &sprite_width - shadow::size(style).px(); diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index 2d0de2d0ae..6a24f11803 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -81,12 +81,12 @@ impl component::Frp for Frp { click <- any(&track_click,&background_click).gate(&frp.allow_click_selection); click_value_update <- click.map2(&base_frp.track_max_width,position_to_normalised_value); - is_dragging <- any( - base_frp.is_dragging_track, - base_frp.is_dragging_background, - base_frp.is_dragging_left_overflow, - base_frp.is_dragging_right_overflow - ); + is_dragging <- any + ( base_frp.is_dragging_track + , base_frp.is_dragging_background + , base_frp.is_dragging_left_overflow + , base_frp.is_dragging_right_overflow + ); drag_movement <- mouse.translation.gate(&is_dragging); delta_value <- drag_movement.map2(&base_frp.track_max_width, |delta,width| (delta.x + delta.y) / width); From 216897fc509b5badf1dfc3a38c07cf23a4eb9f0e Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Wed, 28 Apr 2021 10:00:34 +0200 Subject: [PATCH 04/25] refactor: Rename `NeverDrop` to `Leak` and make it available through the example prelude. --- src/rust/ensogl/example/src/leak.rs | 45 +++++++++++++++++++++++++++ src/rust/ensogl/example/src/lib.rs | 2 ++ src/rust/ensogl/example/src/slider.rs | 29 +++-------------- 3 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 src/rust/ensogl/example/src/leak.rs diff --git a/src/rust/ensogl/example/src/leak.rs b/src/rust/ensogl/example/src/leak.rs new file mode 100644 index 0000000000..5ea29d2f48 --- /dev/null +++ b/src/rust/ensogl/example/src/leak.rs @@ -0,0 +1,45 @@ +//! `Leak` is a utility struct that prevents the wrapped value from being dropped when `Leak` is +//! being dropped. This is achieved by passing the contained value to `std::mem::forget` in the +//! drop implementation iof `Leak`. Can bue used in examples to keep components alive for the whole +//! lifetime of the application. + + + +/// Wrapper that will prevent the wrapped value from being dropped. Instead, the value will be +/// leaked when the `Leak` is dropped. +#[derive(Debug)] +pub struct Leak { + value: Option +} + +impl Leak { + /// Constructor. The passed value will be prevented from being dropped. This will cause memory + /// leaks. + pub fn new(value:T) -> Self { + Self{value:Some(value)} + } + + /// Return a reference to the wrapped value. + pub fn inner(&self) -> &T { + // Guaranteed to never panic as this is always initialised with `Some` in the constructor. + self.value.as_ref().unwrap() + } + + /// Return a mutable reference to the wrapped value. + pub fn inner_mut(&mut self) -> &mut T { + // Guaranteed to never panic as this is always initialised with `Some` in the constructor. + self.value.as_mut().unwrap() + } +} + +impl Drop for Leak { + fn drop(&mut self) { + std::mem::forget(self.value.take()); + } +} + +// impl Debug for Leak { +// fn fmt(&self, f: &mut Formatter<'_>) -> Result { +// f.fmt("Leak{value:{:?}}", self.value.unwrap()) +// } +// } \ No newline at end of file diff --git a/src/rust/ensogl/example/src/lib.rs b/src/rust/ensogl/example/src/lib.rs index 0260b1cd7f..623b3eb005 100644 --- a/src/rust/ensogl/example/src/lib.rs +++ b/src/rust/ensogl/example/src/lib.rs @@ -28,6 +28,7 @@ #[allow(clippy::option_map_unit_fn)] +mod leak; pub mod animation; pub mod complex_shape_system; pub mod dom_symbols; @@ -43,4 +44,5 @@ pub mod text_area; /// Common types that should be visible across the whole crate. pub mod prelude { pub use ensogl_core::prelude::*; + pub use super::leak::*; } diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs index 407db035c5..243a38e529 100644 --- a/src/rust/ensogl/example/src/slider.rs +++ b/src/rust/ensogl/example/src/slider.rs @@ -30,38 +30,18 @@ pub fn entry_point_slider() { }); } -fn make_number_picker(app:&Application) -> NeverDrop { +fn make_number_picker(app:&Application) -> Leak { let slider = app.new_view::(); slider.frp.resize(Vector2(200.0,50.0)); app.display.add_child(&slider); - NeverDrop::new(slider) + Leak::new(slider) } -fn make_range_picker(app:&Application) -> NeverDrop { +fn make_range_picker(app:&Application) -> Leak { let slider = app.new_view::(); slider.frp.resize(Vector2(400.0,50.0)); app.display.add_child(&slider); - NeverDrop::new(slider) -} - -// Utility struct that leaks a struct instead of dropping it. Will cause memory leaks. -struct NeverDrop { - value: Option -} - -impl NeverDrop { - fn new(value:T) -> Self { - NeverDrop{value:Some(value)} - } - fn inner(&self) -> &T { - self.value.as_ref().unwrap() - } -} - -impl Drop for NeverDrop { - fn drop(&mut self) { - std::mem::forget(self.value.take()); - } + Leak::new(slider) } @@ -77,6 +57,7 @@ fn init(app:&Application) { let slider1 = make_number_picker(app); slider1.inner().frp.allow_click_selection(true); + let slider2 = make_number_picker(app); slider2.inner().frp.resize(Vector2(400.0,50.0)); slider2.inner().frp.set_bounds.emit((-100.0,100.0)); From 322b24ec35507bf60ea4e637db763e92bfbfa6c3 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Wed, 28 Apr 2021 13:46:29 +0200 Subject: [PATCH 05/25] refactor:Turn `Bounds` and `Range` into a common struct with named fields. --- src/rust/ensogl/example/src/slider.rs | 7 +- .../ensogl/lib/components/src/selector.rs | 1 + .../lib/components/src/selector/common.rs | 72 +++++++++++-------- .../components/src/selector/common/model.rs | 10 +-- .../lib/components/src/selector/number.rs | 6 +- .../lib/components/src/selector/range.rs | 59 +++++++++------ 6 files changed, 92 insertions(+), 63 deletions(-) diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs index 243a38e529..ddd6338c4b 100644 --- a/src/rust/ensogl/example/src/slider.rs +++ b/src/rust/ensogl/example/src/slider.rs @@ -7,6 +7,7 @@ use ensogl_core::application::Application; use ensogl_core::display::object::ObjectOps; use ensogl_core::system::web; use ensogl_gui_components::selector; +use ensogl_gui_components::selector::Bounds; use ensogl_text_msdf_sys::run_once_initialized; use ensogl_theme as theme; @@ -60,9 +61,9 @@ fn init(app:&Application) { let slider2 = make_number_picker(app); slider2.inner().frp.resize(Vector2(400.0,50.0)); - slider2.inner().frp.set_bounds.emit((-100.0,100.0)); + slider2.inner().frp.set_bounds.emit(Bounds::new(-100.0,100.0)); slider2.inner().set_position_y(50.0); - slider2.inner().frp.use_overflow_bounds((-150.0,200.0)); + slider2.inner().frp.use_overflow_bounds(Bounds::new(-150.0,200.0)); slider2.inner().frp.set_caption(Some("Value:".to_string())); let slider3 = make_range_picker(app); @@ -70,6 +71,6 @@ fn init(app:&Application) { let slider4 = make_range_picker(app); slider4.inner().set_position_y(-200.0); - slider4.inner().frp.use_overflow_bounds((-2.0,3.0)); + slider4.inner().frp.use_overflow_bounds(Bounds::new(-2.0,3.0)); slider4.inner().frp.set_caption(Some("Caption".to_string())); } diff --git a/src/rust/ensogl/lib/components/src/selector.rs b/src/rust/ensogl/lib/components/src/selector.rs index 07dc2d4e46..179b6aa874 100644 --- a/src/rust/ensogl/lib/components/src/selector.rs +++ b/src/rust/ensogl/lib/components/src/selector.rs @@ -6,6 +6,7 @@ mod common; use ensogl_core::application; use ensogl_core::application::Application; +pub use common::Bounds; // ===================== diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/common.rs index c5dd9670ed..51329c6094 100644 --- a/src/rust/ensogl/lib/components/src/selector/common.rs +++ b/src/rust/ensogl/lib/components/src/selector/common.rs @@ -21,17 +21,45 @@ pub use model::*; /// Bounds of a selection. This indicates the lowest and highest value that can be selected in a /// selection component. -pub type Bounds = (f32, f32); +#[derive(Clone,Copy,Debug,Default)] +pub struct Bounds { + /// Start of the bounds interval (inclusive). + pub start : f32, + /// End of the bounds interval (inclusive). + pub end : f32, +} + +impl Bounds { + /// Constructor. + pub fn new(start:f32,end:f32) -> Self { + Bounds{start,end} + } + + /// Return the `Bound` with the lower bound as `start` and the upper bound as `end`. + pub fn sorted(&self) -> Self { + if self.start > self.end { + Bounds{start:self.end,end:self.start} + } else { + self.clone() + } + } +} + +impl From<(f32,f32)> for Bounds { + fn from((start,end): (f32, f32)) -> Self { + Bounds{start,end} + } +} /// Frp utility method to normalise the given value to the given Bounds. -pub fn normalise_value((value,range):&(f32,Bounds)) -> f32 { - (value - range.0) / (range.1 - range.0) +pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 { + (value - bounds.start) / (bounds.end - bounds.start) } /// Frp utility method to compute the absolute value from a normalised value. /// Inverse of `normalise_value`. -pub fn absolute_value((range,normalised_value):&(Bounds,f32)) -> f32 { - ((normalised_value * (range.1 - range.0)) + range.0) +pub fn absolute_value((bounds,normalised_value):&(Bounds,f32)) -> f32 { + ((normalised_value * (bounds.end - bounds.start)) + bounds.start) } /// Returns the normalised value that correspond to the click posiiton on the shape. @@ -43,12 +71,13 @@ pub fn position_to_normalised_value(pos:&Vector2,width:&f32) -> f32 { /// Check whether the given value is within the given bounds. End points are exclusive. fn value_in_bounds(value:f32, bounds:Bounds) -> bool { - value > bounds.0 && value < bounds.1 + value > bounds.start && value < bounds.end } /// Check whether the given bounds are completely contained in the second bounds. -pub fn range_in_bounds(range:Bounds, bounds:Bounds) -> bool { - value_in_bounds(range.0,bounds) && value_in_bounds(range.1,bounds) +pub fn bounds_in_bounds(bounds_inner:Bounds, bounds_outer:Bounds) -> bool { + value_in_bounds(bounds_inner.start,bounds_outer) + && value_in_bounds(bounds_inner.end,bounds_outer) } /// Clamp `value` to the `overflow_bounds`, or to [0, 1] if no bounds are given. @@ -56,37 +85,20 @@ pub fn range_in_bounds(range:Bounds, bounds:Bounds) -> bool { #[allow(clippy::trivially_copy_pass_by_ref)] pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option) -> f32 { if let Some(overflow_bounds) = overflow_bounds{ - value.clamp(overflow_bounds.0,overflow_bounds.1) + value.clamp(overflow_bounds.start,overflow_bounds.end) } else { - value.clamp(0.0, 1.0) + value.clamp(0.0,1.0) } } /// Indicates whether the `value` would be clamped when given to `clamp_with_overflow`. /// For use in FRP `map` method, thus taking references. #[allow(clippy::trivially_copy_pass_by_ref)] -pub fn should_clamp_with_overflow(range:&Bounds, overflow_bounds:&Option) -> bool { +pub fn should_clamp_with_overflow(bounds:&Bounds, overflow_bounds:&Option) -> bool { if let Some(overflow_bounds) = overflow_bounds { - range_in_bounds(*range,*overflow_bounds) - } else { - range_in_bounds(*range,(0.0,1.0)) - } -} - - - -// ============= -// === Range === -// ============= - -pub type Range = (f32, f32); - -/// Return the range with sorted components. -pub fn sorted_range(range:Range) -> Range { - if range.0 > range.1 { - (range.1, range.0) + bounds_in_bounds(*bounds,*overflow_bounds) } else { - range + bounds_in_bounds(*bounds,(0.0,1.0).into()) } } diff --git a/src/rust/ensogl/lib/components/src/selector/common/model.rs b/src/rust/ensogl/lib/components/src/selector/common/model.rs index 4cb439b863..8bf2083322 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/model.rs @@ -207,12 +207,12 @@ impl Model { self.track.right.set(value); } - pub fn set_background_range(&self, value:common::Range, size:Vector2) { - self.track.left.set(value.0); - self.track.right.set(value.1); + pub fn set_background_range(&self, value:common::Bounds, size:Vector2) { + self.track.left.set(value.start); + self.track.right.set(value.end); - self.track_handle_left.set_position_x(value.0 * size.x - size.x / 2.0); - self.track_handle_right.set_position_x(value.1 * size.x - size.x / 2.0); + self.track_handle_left.set_position_x(value.start * size.x - size.x / 2.0); + self.track_handle_right.set_position_x(value.end * size.x - size.x / 2.0); } pub fn set_center_label_content(&self, value:f32) { diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index 6a24f11803..05eff893bd 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -66,7 +66,9 @@ impl component::Frp for Frp { norm_value <- all2(&frp.value,&frp.bounds).map(normalise_value); _has_overflow_bounds <- frp.use_overflow_bounds.map(|value| value.is_none()); normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map(|(overflow_bounds,bounds)| - overflow_bounds.map(|(lower,upper)| (normalise_value(&(lower,*bounds)),normalise_value(&(upper,*bounds))) ) + overflow_bounds.map(|Bounds{start,end}| + Bounds::new(normalise_value(&(start,*bounds)),normalise_value(&(end,*bounds))) + ) ); has_underflow <- norm_value.map(|value| *value < 0.0); has_overflow <- norm_value.map(|value| *value > 1.0); @@ -98,7 +100,7 @@ impl component::Frp for Frp { } // Init defaults. - frp.set_bounds((0.0,1.0)); + frp.set_bounds(Bounds::new(0.0,1.0)); frp.allow_click_selection(false); frp.use_overflow_bounds(None); frp.set_value(0.5); diff --git a/src/rust/ensogl/lib/components/src/selector/range.rs b/src/rust/ensogl/lib/components/src/selector/range.rs index 39d4d9dd90..e9f09c8ed7 100644 --- a/src/rust/ensogl/lib/components/src/selector/range.rs +++ b/src/rust/ensogl/lib/components/src/selector/range.rs @@ -11,7 +11,7 @@ use crate::component; use crate::selector::common::base_frp::BaseFrp; use super::common::*; -use super::common::Range; +use super::common::Bounds; @@ -21,7 +21,7 @@ use super::common::Range; ensogl_core::define_endpoints! { Input { - set_value(Range), + set_value(Bounds), resize(Vector2), set_bounds(Bounds), use_overflow_bounds(Option), @@ -30,7 +30,7 @@ ensogl_core::define_endpoints! { set_right_corner_round(bool), } Output { - value(Range), + value(Bounds), bounds(Bounds), } } @@ -60,15 +60,16 @@ impl component::Frp for Frp { eval frp.set_right_corner_round((value) model.right_corner_round(*value)); // Intern State Vales - norm_value <- all2(&frp.value,&frp.bounds).map(|((lower,upper),bounds)|{ - (normalise_value(&(*lower,*bounds)),normalise_value(&(*upper,*bounds))) + norm_value <- all2(&frp.value,&frp.bounds).map(|(Bounds{start,end},bounds)|{ + Bounds::new(normalise_value(&(*start,*bounds)),normalise_value(&(*end,*bounds))) }); - _has_overflow_bounds <- frp.use_overflow_bounds.map(|value| value.is_none()); normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map(|(overflow_bounds,bounds)| - overflow_bounds.map(|(lower,upper)| (normalise_value(&(lower,*bounds)),normalise_value(&(upper,*bounds))) ) + overflow_bounds.map(|Bounds{start,end}| + Bounds::new(normalise_value(&(start,*bounds)),normalise_value(&(end,*bounds))) + ) ); - has_underflow <- norm_value.map(|value| (value.0.min(value.1)) < 0.0); - has_overflow <- norm_value.map(|value| (value.0.max(value.1)) > 1.0); + has_underflow <- norm_value.map(|value| (value.start.min(value.end)) < 0.0); + has_overflow <- norm_value.map(|value| (value.start.max(value.end)) > 1.0); // Slider Updates update_slider <- all(&norm_value,&frp.resize); @@ -76,39 +77,51 @@ impl component::Frp for Frp { eval has_underflow ((underflow) model.show_left_overflow(*underflow)); eval has_overflow ((overflow) model.show_right_overflow(*overflow)); eval frp.value((value) { - model.set_left_label_content(value.0); - model.set_right_label_content(value.1); + model.set_left_label_content(value.start); + model.set_right_label_content(value.end); }); // Mouse IO let change_left_and_right_value = base_frp.is_dragging_track.clone_ref(); - change_left_value <- base_frp.is_dragging_left_handle || base_frp.is_dragging_left_overflow; - change_right_value <- base_frp.is_dragging_right_handle || base_frp.is_dragging_right_overflow; + change_left_value <- base_frp.is_dragging_left_handle + || base_frp.is_dragging_left_overflow; + change_right_value <- base_frp.is_dragging_right_handle + || base_frp.is_dragging_right_overflow; drag_movement <- mouse.translation.gate(&base_frp.is_dragging_any); - drag_delta <- drag_movement.map2(&base_frp.track_max_width, |delta,width| (delta.x + delta.y) / width); + drag_delta <- drag_movement.map2(&base_frp.track_max_width, |delta,width| + (delta.x + delta.y) / width + ); drag_center_delta <- drag_delta.gate(&change_left_and_right_value); - center_update <- drag_center_delta.map2(&norm_value,|delta,(l,u)| (l+delta,u+delta)); + center_update <- drag_center_delta.map2(&norm_value,|delta,Bounds{start,end}| + Bounds::new(start+delta,end+delta) + ); drag_left_delta <- drag_delta.gate(&change_left_value); - left_update <- drag_left_delta.map2(&norm_value,|delta,(l,u)| (l+delta,*u)); + left_update <- drag_left_delta.map2(&norm_value,|delta,Bounds{start,end}| + Bounds::new(start+delta,*end) + ); drag_right_delta <- drag_delta.gate(&change_right_value); - right_update <- drag_right_delta.map2(&norm_value,|delta,(l,u)| (*l,u+delta)); + right_update <- drag_right_delta.map2(&norm_value,|delta,Bounds{start,end}| + Bounds::new(*start,end+delta) + ); any_update <- any3(¢er_update,&left_update,&right_update); - is_in_bounds <- any_update.map2(&normalised_overflow_bounds, |range,bounds| should_clamp_with_overflow(range,bounds)); - new_value_absolute <- all(&frp.set_bounds,&any_update).map(|(bounds,(lower,upper))|{ - sorted_range((absolute_value(&(*bounds,*lower)),absolute_value(&(*bounds,*upper)))) - }); + is_in_bounds <- any_update.map2(&normalised_overflow_bounds, |range,bounds| + should_clamp_with_overflow(range,bounds) + ); + new_value_absolute <- all(&frp.set_bounds,&any_update).map(|(bounds,Bounds{start,end})| + Bounds::new(absolute_value(&(*bounds,*start)),absolute_value(&(*bounds,*end))).sorted() + ); frp.source.value <+ new_value_absolute.gate(&is_in_bounds); } // Init defaults; - frp.set_bounds((0.0,1.0)); + frp.set_bounds(Bounds::new(0.0,1.0)); frp.use_overflow_bounds(None); - frp.set_value((0.25,0.75)); + frp.set_value(Bounds::new(0.25,0.75)); frp.set_left_corner_round(true); frp.set_right_corner_round(true); From 7860a3035aff93242782d72deb1da88562c60ed4 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Wed, 28 Apr 2021 16:27:01 +0200 Subject: [PATCH 06/25] refactor: Add tests. --- src/rust/Cargo.lock | 7 + src/rust/ensogl/lib/components/Cargo.toml | 1 + .../lib/components/src/selector/common.rs | 269 +++++++++++++++++- 3 files changed, 267 insertions(+), 10 deletions(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 565d90d691..85f04ac080 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -799,6 +799,7 @@ dependencies = [ "ensogl-core 0.1.0", "ensogl-text 0.1.0", "ensogl-theme 0.1.0", + "float_eq 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-test 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -948,6 +949,11 @@ dependencies = [ "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "float_eq" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fnv" version = "1.0.7" @@ -3273,6 +3279,7 @@ dependencies = [ "checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" "checksum flo_stream 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b02e0d3667b27514149c1ac9b372d700f3e6df4bbaf6b7c5df12915de2996049" "checksum float-cmp 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +"checksum float_eq 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb23b6902f3cdc0544f9916b4c092f46f4ff984e219d5a0c538b6b3539885af3" "checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" diff --git a/src/rust/ensogl/lib/components/Cargo.toml b/src/rust/ensogl/lib/components/Cargo.toml index d364fecc65..6d70b71dd0 100644 --- a/src/rust/ensogl/lib/components/Cargo.toml +++ b/src/rust/ensogl/lib/components/Cargo.toml @@ -19,3 +19,4 @@ ensogl-theme = { path = "../theme" } [dev-dependencies] wasm-bindgen-test = { version = "0.3.8" } +float_eq = "0.5" \ No newline at end of file diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/common.rs index 51329c6094..0cdb3db1d3 100644 --- a/src/rust/ensogl/lib/components/src/selector/common.rs +++ b/src/rust/ensogl/lib/components/src/selector/common.rs @@ -43,6 +43,10 @@ impl Bounds { self.clone() } } + + pub fn width(&self) -> f32 { + (self.end - self.start) + } } impl From<(f32,f32)> for Bounds { @@ -53,25 +57,30 @@ impl From<(f32,f32)> for Bounds { /// Frp utility method to normalise the given value to the given Bounds. pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 { - (value - bounds.start) / (bounds.end - bounds.start) + let width = bounds.width(); + if width == 0.0 { return 0.0 } + (value - bounds.start) / width } /// Frp utility method to compute the absolute value from a normalised value. /// Inverse of `normalise_value`. pub fn absolute_value((bounds,normalised_value):&(Bounds,f32)) -> f32 { - ((normalised_value * (bounds.end - bounds.start)) + bounds.start) + ((normalised_value * bounds.width()) + bounds.start) } -/// Returns the normalised value that correspond to the click posiiton on the shape. +/// Returns the normalised value that correspond to the click position on the shape. +/// Note that the shape is centered on (0,0), thus half the width extends into the negative values. /// For use in FRP `map` method, thus taking references. #[allow(clippy::trivially_copy_pass_by_ref)] pub fn position_to_normalised_value(pos:&Vector2,width:&f32) -> f32 { - ((pos.x / (width/2.0)) + 1.0) / 2.0 + if *width == 0.0 { return 0.0 } + ((pos.x / (width / 2.0)) + 1.0) / 2.0 } -/// Check whether the given value is within the given bounds. End points are exclusive. +/// Check whether the given value is within the given bounds. fn value_in_bounds(value:f32, bounds:Bounds) -> bool { - value > bounds.start && value < bounds.end + let bounds_sorted = bounds.sorted(); + value >= bounds_sorted.start && value <= bounds_sorted.end } /// Check whether the given bounds are completely contained in the second bounds. @@ -91,7 +100,7 @@ pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option) -> f32 { } } -/// Indicates whether the `value` would be clamped when given to `clamp_with_overflow`. +/// Indicates whether the `bounds` would be clamped when given to `clamp_with_overflow`. /// For use in FRP `map` method, thus taking references. #[allow(clippy::trivially_copy_pass_by_ref)] pub fn should_clamp_with_overflow(bounds:&Bounds, overflow_bounds:&Option) -> bool { @@ -112,7 +121,8 @@ pub fn should_clamp_with_overflow(bounds:&Bounds, overflow_bounds:&Option frp::Stream { +pub fn shape_is_dragged +(network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { frp::extend! { network mouse_up <- mouse.up.constant(()); mouse_down <- mouse.down.constant(()); @@ -125,14 +135,253 @@ pub fn shape_is_dragged(network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) /// Returns the position of a mouse down on a shape. The position is given relative to the origin /// of the shape position. -pub fn relative_shape_click_position(model:&Model, network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { +pub fn relative_shape_click_position +(model:&Model, network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { let model = model.clone_ref(); frp::extend! { network mouse_down <- mouse.down.constant(()); over_shape <- bool(&shape.mouse_out,&shape.mouse_over); mouse_down_over_shape <- mouse_down.gate(&over_shape); background_click_positon <- mouse.position.sample(&mouse_down_over_shape); - background_click_positon <- background_click_positon.map(move |pos| pos - model.position().xy()); + background_click_positon <- background_click_positon.map(move |pos| + pos - model.position().xy() + ); } background_click_positon } + + +#[cfg(test)] +mod tests { + use super::*; + use float_eq::assert_float_eq; + use std::f32::NAN; + + #[test] + fn test_normalise_value() { + let test = |start,end,value,expected| { + let bounds = Bounds::new(start,end); + let normalised = normalise_value(&(value,bounds)); + assert_float_eq!(normalised,expected,ulps<=7) + }; + + test(0.0,1.0,0.0,0.0); + test(0.0,1.0,0.1,0.1); + test(0.0,1.0,0.2,0.2); + test(0.0,1.0,0.3,0.3); + test(0.0,1.0,0.4,0.4); + test(0.0,1.0,0.5,0.5); + test(0.0,1.0,0.6,0.6); + test(0.0,1.0,0.7,0.7); + test(0.0,1.0,0.7,0.7); + test(0.0,1.0,0.7,0.7); + test(0.0,1.0,1.0,1.0); + + test(0.0,1.0,-2.0,-2.0); + test(0.0,1.0,-1.0,-1.0); + test(0.0,1.0,2.0,2.0); + test(0.0,1.0,3.0,3.0); + + test(-1.0,1.0,-1.0,0.0); + test(-1.0,1.0,-0.5,0.25); + test(-1.0,1.0,0.0,0.5); + test(-1.0,1.0,0.5,0.75); + test(-1.0,1.0,1.0,1.0); + + test(1.0,-1.0,-1.0,1.0); + test(1.0,-1.0,-0.5,0.75); + test(1.0,-1.0,0.0,0.5); + test(1.0,-1.0,0.5,0.25); + test(1.0,-1.0,1.0,0.0); + + test(-10.0,20.0,-10.0,0.0); + test(-10.0,20.0,20.0,1.0); + test(-10.0,20.0,0.0,0.33333333); + + test(-999999999.0,999999999.0,-999999999.0,0.0); + test(-999999999.0,999999999.0,0.0,0.5); + test(-999999999.0,999999999.0,999999999.0,1.0); + + test(0.0,0.0,1.0,0.0); + test(0.0,0.0,0.0,0.0); + test(0.0,0.0,-1.0,0.0); + } + + #[test] + fn test_absolute_value() { + let test = |start,end,value,expected| { + let bounds = Bounds::new(start,end); + let normalised = absolute_value(&(bounds,value)); + assert_float_eq!(normalised,expected,ulps<=7) + }; + + test(0.0,1.0,0.0,0.0); + test(0.0,1.0,0.1,0.1); + test(0.0,1.0,0.2,0.2); + test(0.0,1.0,0.3,0.3); + test(0.0,1.0,0.4,0.4); + test(0.0,1.0,0.5,0.5); + test(0.0,1.0,0.6,0.6); + test(0.0,1.0,0.7,0.7); + test(0.0,1.0,0.7,0.7); + test(0.0,1.0,0.7,0.7); + test(0.0,1.0,1.0,1.0); + + test(0.0,1.0,-2.0,-2.0); + test(0.0,1.0,-1.0,-1.0); + test(0.0,1.0,2.0,2.0); + test(0.0,1.0,3.0,3.0); + + test(-1.0,1.0,0.0,-1.0); + test(-1.0,1.0,0.25,-0.5); + test(-1.0,1.0,0.5,0.0); + test(-1.0,1.0,0.75,0.5); + test(-1.0,1.0,1.0,1.0); + + test(1.0,-1.0,1.0,-1.0); + test(1.0,-1.0,0.75,-0.5); + test(1.0,-1.0,0.5,0.0); + test(1.0,-1.0,0.25,0.5); + test(1.0,-1.0,0.0,1.0); + + test(-10.0,20.0,0.0,-10.0); + test(-10.0,20.0,1.0,20.0); + test(-10.0,20.0,0.33333333,0.0); + + test(-999999999.0,999999999.0,0.0,-999999999.0); + test(-999999999.0,999999999.0,0.5,0.0); + test(-999999999.0,999999999.0,1.0,999999999.0); + + test(0.0,0.0,1.0,0.0); + test(1.0,1.0,1.0,1.0); + test(1.0,1.0,2.0,1.0); + test(1.0,1.0,-2.0,1.0); + } + + + #[test] + fn test_position_to_normalised_value() { + let test = |pos,width,expected| { + let result = position_to_normalised_value(&pos,&width); + assert_float_eq!(result,expected,ulps<=7) + }; + + for &y in &[-100.0, 0.0, 100.0, NAN] { + test(Vector2::new(50.0,y),100.0,1.0); + test(Vector2::new(0.0,y),100.0,0.5); + test(Vector2::new(-50.0,y),100.0,0.0); + + test(Vector2::new(100.0,y),100.0,1.5); + test(Vector2::new(-100.0,y),100.0,-0.5); + test(Vector2::new(150.0,y),100.0,2.0); + test(Vector2::new(-150.0,y),100.0,-1.0); + test(Vector2::new(200.0,y),100.0,2.5); + test(Vector2::new(-200.0,y),100.0,-1.5); + + test(Vector2::new(-200.0,y),0.0,0.0); + } + } + + #[test] + fn test_value_in_bounds() { + let test = |start,end,value,expected| { + let result = value_in_bounds(value,Bounds::new(start,end)); + assert_eq!(result,expected, "Testing whether {} in ]{},{}[", value,start,end) + }; + + test(0.0,1.0,0.0,true); + test(0.0,1.0,0.5,true); + test(0.0,1.0,1.0,true); + test(0.0,1.0,1.00001,false); + test(0.0,1.0,-0.00001,false); + + test(0.0,10.0,10.0,true); + test(0.0,10.0,9.999999,true); + test(0.0,10.0,11.0,false); + + test(-100.0,10.0,11.0,false); + test(-101.0,10.0,-100.0,true); + test(-101.0,10.0,-101.0,true); + test(-101.0,10.0,-101.1,false); + + test(0.0,0.0,0.0,true); + test(0.0,0.0,1.0,false); + test(0.0,0.0,-1.0,false); + } + + #[test] + fn test_bounds_in_bounds() { + let test = |start1,end1,start2,end2,expected| { + let result = bounds_in_bounds(Bounds::new(start1,start2),Bounds::new(start2,end2)); + assert_eq!(result,expected, + "Testing whether ]{},{}[ in ]{},{}[", start1,end1,start2,end2); + }; + + test(0.0,1.0,0.0,1.0,true); + test(0.0,1.0,1.0,2.0,false); + test(0.0,1.0,0.5,2.0,false); + test(0.0,1.0,-100.0,100.0,true); + test(0.0,1.0,-100.0,-99.0,false); + test(0.0,1.0,0.1,0.9,false); + test(-100.0,200.0,50.0,75.0,false); + test(-100.0,200.0,-50.0,75.0,false); + test(-100.0,200.0,-50.0,-75.0,false); + test(-100.0,200.0,-50.0,99999.0,false); + test(-100.0,200.0,-99999.0,0.0,true); + test(-100.0,200.0,-99999.0,99999.0,true); + + test(0.0,0.0,0.0,0.0,true); + test(0.0,0.0,-1.0,2.0,true); + test(0.0,0.0,1.0,2.0,false); + } + + #[test] + fn test_clamp_with_overflow() { + let test = |value,bounds,expected| { + let result = clamp_with_overflow(&value,&bounds); + assert_float_eq!(result,expected,ulps<=7) + }; + + test(0.0,Some(Bounds::new(0.0,1.0)), 0.0); + test(-1.0,Some(Bounds::new(0.0,1.0)), 0.0); + test(2.0,Some(Bounds::new(0.0,1.0)), 1.0); + + test(-1.0,None, 0.0); + test(2.0,None,1.0); + + test(-999.0,Some(Bounds::new(-1.0,100.0)), -1.0); + test(999.0,Some(Bounds::new(-1.0,100.0)), 100.0); + test(-1.0,Some(Bounds::new(-1.0,100.0)), -1.0); + test(0.0,Some(Bounds::new(-1.0,100.0)), 0.0); + test(99.0,Some(Bounds::new(-1.0,100.0)), 99.0); + test(100.0,Some(Bounds::new(-1.0,100.0)), 100.0); + test(100.01,Some(Bounds::new(-1.0,100.0)), 100.0); + } + + #[test] + fn test_should_clamp_with_overflow() { + let test = |inner,outer,expected| { + let result = should_clamp_with_overflow(&inner,&outer); + assert_eq!(result,expected); + }; + + test(Bounds::new(0.0,1.0),Some(Bounds::new(0.0,1.0)),true); + test(Bounds::new(0.0,1.0),Some(Bounds::new(1.0,2.0)),false); + test(Bounds::new(0.0,1.0),Some(Bounds::new(0.5,2.0)),false); + test(Bounds::new(0.0,1.0),Some(Bounds::new(-100.0,100.0)),true); + test(Bounds::new(0.0,1.0),Some(Bounds::new(-100.0,-99.0)),false); + test(Bounds::new(0.0,1.0),Some(Bounds::new(0.1,0.9)),false); + test(Bounds::new(-100.0,200.0),Some(Bounds::new(50.0,75.0)),false); + test(Bounds::new(-100.0,200.0),Some(Bounds::new(-50.0,75.0)),false); + test(Bounds::new(-100.0,200.0),Some(Bounds::new(-50.0,-75.0)),false); + test(Bounds::new(-100.0,200.0),Some(Bounds::new(-50.0,99999.0)),false); + test(Bounds::new(-100.0,200.0),Some(Bounds::new(-99999.0,0.0)),false); + test(Bounds::new(-100.0,200.0),Some(Bounds::new(-99999.0,99999.0)),true); + test(Bounds::new(-100.0,0.0),None,false); + test(Bounds::new(0.1,1.1),None,false); + test(Bounds::new(-9.1,2.1),None,false); + test(Bounds::new(0.25,0.7),None,true); + + test(Bounds::new(0.0,0.0),None,true); + } +} \ No newline at end of file From 5ef4bbe8478605242bf455cab5064c90835f28b4 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 09:34:21 +0200 Subject: [PATCH 07/25] fix: Remove unused code. --- src/rust/ensogl/example/src/leak.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/rust/ensogl/example/src/leak.rs b/src/rust/ensogl/example/src/leak.rs index 5ea29d2f48..add02e3f3b 100644 --- a/src/rust/ensogl/example/src/leak.rs +++ b/src/rust/ensogl/example/src/leak.rs @@ -37,9 +37,3 @@ impl Drop for Leak { std::mem::forget(self.value.take()); } } - -// impl Debug for Leak { -// fn fmt(&self, f: &mut Formatter<'_>) -> Result { -// f.fmt("Leak{value:{:?}}", self.value.unwrap()) -// } -// } \ No newline at end of file From ff06c1e11ff383fed803ff0287c5d89dc2a7ecd8 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 09:50:02 +0200 Subject: [PATCH 08/25] dox: Add missing doc. --- src/rust/ensogl/lib/components/src/selector/common.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/common.rs index 0cdb3db1d3..cd53bbcfc2 100644 --- a/src/rust/ensogl/lib/components/src/selector/common.rs +++ b/src/rust/ensogl/lib/components/src/selector/common.rs @@ -44,6 +44,7 @@ impl Bounds { } } + /// Return the distance between start and end point. pub fn width(&self) -> f32 { (self.end - self.start) } From f8702afdbe14c77fc88912d68472821b99121721 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 10:14:16 +0200 Subject: [PATCH 09/25] redactor+doc: Better document decimal_aligned helper. Remove unused code. --- .../components/src/selector/common/model.rs | 39 +++++++++++-------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/common/model.rs b/src/rust/ensogl/lib/components/src/selector/common/model.rs index 8bf2083322..3c312c865d 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/model.rs @@ -21,6 +21,8 @@ const LABEL_OFFSET : f32 = 13.0; // === Utilities - Decimal Aligned Text Field === // ============================================== +/// Utility wrapper for a text field containing a float. Centers the string representation of the +/// float on the decimal separator. mod decimal_aligned { use super::*; @@ -35,7 +37,7 @@ mod decimal_aligned { fn init(&self, app: &Application, model: &Model, _style: &StyleWatchFrp) { let frp = &self; let network = &frp.network; - let _scene = app.display.scene(); + let _scene = app.display.scene(); frp::extend! { network formatted <- frp.set_content.map(|value| format!("{:.2}", value)); @@ -44,42 +46,45 @@ mod decimal_aligned { left <- formatted.map(|s| s.split('.').next().map(|s| s.to_string())).unwrap(); model.label_left.set_content <+ left; - model.label.set_content <+ formatted; + model.label_full.set_content <+ formatted; - eval model.label_left.width((offset) model.label.set_position_x(-offset-LABEL_OFFSET)); + eval model.label_left.width((offset) model.label_full.set_position_x(-offset-LABEL_OFFSET)); } } } #[derive(Clone,CloneRef,Debug)] pub struct Model { - root : display::object::Instance, - label : text::Area, + /// Root object. Required as the rendered text label will have an offset relative to the + /// base position of the root, depending on the position of the decimal separator. + root : display::object::Instance, + /// Label containing the text to display. This is the label that will be shown. + label_full : text::Area, + /// This label contains the text to the left of the decimal. This is here, so we can get + /// information about the text width of this portion of the label. This label will + /// not appear in the UI. label_left : text::Area, - label_right: text::Area, } impl component::Model for Model { fn new(app:&Application) -> Self { - let logger = Logger::new("DecimalAlignedLabel"); - let root = display::object::Instance::new(&logger); - let label = app.new_view::(); - let label_left = app.new_view::(); - let label_right = app.new_view::(); + let logger = Logger::new("DecimalAlignedLabel"); + let root = display::object::Instance::new(&logger); + let label_full = app.new_view::(); + let label_left = app.new_view::(); - label.remove_from_scene_layer_DEPRECATED(&app.display.scene().layers.main); - label.add_to_scene_layer_DEPRECATED(&app.display.scene().layers.label); + label_full.remove_from_scene_layer_DEPRECATED(&app.display.scene().layers.main); + label_full.add_to_scene_layer_DEPRECATED(&app.display.scene().layers.label); - root.add_child(&label); + root.add_child(&label_full); root.add_child(&label_left); - root.add_child(&label_right); - Self{root,label,label_left,label_right} + Self{root,label_full,label_left} } } impl display::Object for Model { - fn display_object(&self) -> &display::object::Instance { self.label.display_object() } + fn display_object(&self) -> &display::object::Instance { self.label_full.display_object() } } pub type FloatLabel = crate::component::Component; From d0c29e8c0ecc2ae49b9054b1ee3b7d43bd8d9882 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 10:52:05 +0200 Subject: [PATCH 10/25] doc: Add reference to issue about locales. --- src/rust/ensogl/lib/components/src/selector/common/model.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rust/ensogl/lib/components/src/selector/common/model.rs b/src/rust/ensogl/lib/components/src/selector/common/model.rs index 3c312c865d..5ea8fdfd5f 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/model.rs @@ -43,6 +43,7 @@ mod decimal_aligned { formatted <- frp.set_content.map(|value| format!("{:.2}", value)); // FIXME: the next line is locale dependent. We need a way to get the current locale // dependent decimal separator for this. + // See https://github.com/enso-org/ide/issues/1542 for progress on this. left <- formatted.map(|s| s.split('.').next().map(|s| s.to_string())).unwrap(); model.label_left.set_content <+ left; From a2ef02bff7ea6b67eae05173b6093dd81b208332 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 10:55:49 +0200 Subject: [PATCH 11/25] refactor: Make utility fields public instead of marking them asd dead code. --- .../lib/components/src/selector/common/shape.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/common/shape.rs b/src/rust/ensogl/lib/components/src/selector/common/shape.rs index f39eff40e9..868a375bd3 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/shape.rs @@ -14,12 +14,11 @@ use crate::shadow; /// Utility struct that contains the background shape for the selector components, as well as some /// meta information about it. This information can be used to align other shapes with the /// background. -#[allow(dead_code)] struct Background { - width : Var, - height : Var, - corner_radius : Var, - shape : AnyShape, + pub width : Var, + pub height : Var, + pub corner_radius : Var, + pub shape : AnyShape, } impl Background { @@ -120,11 +119,10 @@ pub mod track { /// Utility struct that contains the overflow shape, and some metadata that can be used to place and /// align it. -#[allow(dead_code)] struct OverFlowShape { - width : Var, - height : Var, - shape : AnyShape + pub width : Var, + pub height : Var, + pub shape : AnyShape } impl OverFlowShape { From 3fe425ea22fff6a242fa6ed12a08a7d7d1c60883 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 10:59:03 +0200 Subject: [PATCH 12/25] style: Formatting and alignment. --- .../components/src/selector/common/shape.rs | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/common/shape.rs b/src/rust/ensogl/lib/components/src/selector/common/shape.rs index 868a375bd3..25ed1ff3d8 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/shape.rs @@ -25,6 +25,7 @@ impl Background { fn new(corner_left:&Var, corner_right:&Var, style:&StyleWatch) -> Background { let sprite_width : Var = "input_size.x".into(); let sprite_height : Var = "input_size.y".into(); + let width = &sprite_width - shadow::size(style).px(); let height = &sprite_height - shadow::size(style).px(); let corner_radius = &height/2.0; @@ -71,8 +72,10 @@ pub mod io_rect { () { let sprite_width : Var = "input_size.x".into(); let sprite_height : Var = "input_size.y".into(); - let rect = Rect((&sprite_width,&sprite_height)); - let shape = rect.fill(HOVER_COLOR); + + let rect = Rect((&sprite_width,&sprite_height)); + let shape = rect.fill(HOVER_COLOR); + shape.into() } } @@ -93,19 +96,19 @@ pub mod track { ensogl_core::define_shape_system! { (style:Style,left:f32,right:f32,corner_left:f32,corner_right:f32) { - let background = Background::new(&corner_left,&corner_right,style); - let width = background.width; - let height = background.height; - - let track_width = (&right - &left) * &width; - let track_start = left * &width; - let track = Rect((&track_width,&height)); - let track = track.translate_x(&track_start + (track_width / 2.0) ); - let track = track.translate_x(-width/2.0); - let track = track.intersection(&background.shape); - - let track_color = style.get_color(theme::component::slider::track::color); - let track = track.fill(track_color); + let background = Background::new(&corner_left,&corner_right,style); + let width = background.width; + let height = background.height; + + let track_width = (&right - &left) * &width; + let track_start = left * &width; + let track = Rect((&track_width,&height)); + let track = track.translate_x(&track_start + (track_width / 2.0) ); + let track = track.translate_x(-width/2.0); + let track = track.intersection(&background.shape); + + let track_color = style.get_color(theme::component::slider::track::color); + let track = track.fill(track_color); track.into() } } @@ -129,11 +132,12 @@ impl OverFlowShape { fn new(style:&StyleWatch) -> Self { let sprite_width : Var = "input_size.x".into(); let sprite_height : Var = "input_size.y".into(); - let width = &sprite_width - shadow::size(style).px(); - let height = &sprite_height - shadow::size(style).px(); + + let width = &sprite_width - shadow::size(style).px(); + let height = &sprite_height - shadow::size(style).px(); let overflow_color = style.get_color(theme::component::slider::overflow::color); - let shape = Triangle(&sprite_height/6.0,&sprite_height/6.0); - let shape = shape.fill(&overflow_color); + let shape = Triangle(&sprite_height/6.0,&sprite_height/6.0); + let shape = shape.fill(&overflow_color); let hover_area = Circle(&height); let hover_area = hover_area.fill(HOVER_COLOR); From b19a52370d4bb8858cf8ab551a670919e767752a Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 11:03:54 +0200 Subject: [PATCH 13/25] style: Code formatting. --- .../lib/components/src/selector/number.rs | 104 ++++++++++-------- .../lib/components/src/selector/range.rs | 16 +-- 2 files changed, 67 insertions(+), 53 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index 05eff893bd..b977e45f51 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -48,56 +48,68 @@ impl component::Frp for Frp { let track_shape_system = scene.shapes.shape_system(PhantomData::); track_shape_system.shape_system.set_pointer_events(false); - let background_click = relative_shape_click_position(model, network, &model.background.events, mouse); - let track_click = relative_shape_click_position(model, network, &model.track.events, mouse); + let background_click = relative_shape_click_position( + model,network,&model.background.events,mouse); + let track_click = relative_shape_click_position( + model,network,&model.track.events,mouse); frp::extend! { network - // Rebind - frp.source.bounds <+ frp.set_bounds; - frp.source.value <+ frp.set_value; - - // Simple Inputs - eval frp.set_caption((caption) model.set_caption_left(caption.clone())); - eval frp.set_left_corner_round ((value) model.left_corner_round(*value)); - eval frp.set_right_corner_round((value) model.right_corner_round(*value)); - - // Intern State Vales - norm_value <- all2(&frp.value,&frp.bounds).map(normalise_value); - _has_overflow_bounds <- frp.use_overflow_bounds.map(|value| value.is_none()); - normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map(|(overflow_bounds,bounds)| - overflow_bounds.map(|Bounds{start,end}| - Bounds::new(normalise_value(&(start,*bounds)),normalise_value(&(end,*bounds))) - ) + // Rebind + frp.source.bounds <+ frp.set_bounds; + frp.source.value <+ frp.set_value; + + // Simple Inputs + eval frp.set_caption((caption) model.set_caption_left(caption.clone())); + eval frp.set_left_corner_round ((value) model.left_corner_round(*value)); + eval frp.set_right_corner_round((value) model.right_corner_round(*value)); + + // Internal + norm_value <- all2(&frp.value,&frp.bounds).map(normalise_value); + normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map( + |(overflow_bounds,bounds)| + overflow_bounds.map(|Bounds{start,end}| + Bounds::new( + normalise_value(&(start,*bounds)),normalise_value(&(end,*bounds))) + ) + ); + has_underflow <- norm_value.map(|value| *value < 0.0); + has_overflow <- norm_value.map(|value| *value > 1.0); + + // Value Updates + eval norm_value((value) model.set_background_value(*value)); + eval has_underflow ((underflow) model.show_left_overflow(*underflow)); + eval has_overflow ((overflow) model.show_right_overflow(*overflow)); + eval frp.value((value) model.set_center_label_content(*value)); + + // Mouse IO + click <- any(&track_click,&background_click).gate(&frp.allow_click_selection); + click_value_update <- click.map2( + &base_frp.track_max_width, + position_to_normalised_value + ); + + is_dragging <- any + ( base_frp.is_dragging_track + , base_frp.is_dragging_background + , base_frp.is_dragging_left_overflow + , base_frp.is_dragging_right_overflow ); - has_underflow <- norm_value.map(|value| *value < 0.0); - has_overflow <- norm_value.map(|value| *value > 1.0); - - // Value Updates - eval norm_value((value) model.set_background_value(*value)); - eval has_underflow ((underflow) model.show_left_overflow(*underflow)); - eval has_overflow ((overflow) model.show_right_overflow(*overflow)); - eval frp.value((value) model.set_center_label_content(*value)); - - // Mouse IO - click <- any(&track_click,&background_click).gate(&frp.allow_click_selection); - click_value_update <- click.map2(&base_frp.track_max_width,position_to_normalised_value); - - is_dragging <- any - ( base_frp.is_dragging_track - , base_frp.is_dragging_background - , base_frp.is_dragging_left_overflow - , base_frp.is_dragging_right_overflow - ); - - drag_movement <- mouse.translation.gate(&is_dragging); - delta_value <- drag_movement.map2(&base_frp.track_max_width, |delta,width| (delta.x + delta.y) / width); - - drag_value_update <- delta_value.map2(&norm_value,|delta,value|*delta+*value); - value_update <- any(&click_value_update,&drag_value_update); - clamped_update <- value_update.map2(&normalised_overflow_bounds,clamp_with_overflow); - frp.source.value <+ all(&frp.set_bounds,&clamped_update).map(absolute_value); - } + + drag_movement <- mouse.translation.gate(&is_dragging); + delta_value <- drag_movement.map2( + &base_frp.track_max_width, + |delta,width| (delta.x + delta.y) / width + ); + + drag_value_update <- delta_value.map2(&norm_value,|delta,value|*delta+*value); + value_update <- any(&click_value_update,&drag_value_update); + clamped_update <- value_update.map2( + &normalised_overflow_bounds, + clamp_with_overflow + ); + frp.source.value <+ all(&frp.set_bounds,&clamped_update).map(absolute_value); + } // Init defaults. frp.set_bounds(Bounds::new(0.0,1.0)); diff --git a/src/rust/ensogl/lib/components/src/selector/range.rs b/src/rust/ensogl/lib/components/src/selector/range.rs index e9f09c8ed7..e3251de6a7 100644 --- a/src/rust/ensogl/lib/components/src/selector/range.rs +++ b/src/rust/ensogl/lib/components/src/selector/range.rs @@ -59,11 +59,12 @@ impl component::Frp for Frp { eval frp.set_left_corner_round ((value) model.left_corner_round(*value)); eval frp.set_right_corner_round((value) model.right_corner_round(*value)); - // Intern State Vales - norm_value <- all2(&frp.value,&frp.bounds).map(|(Bounds{start,end},bounds)|{ + // Internal + norm_value <- all2(&frp.value,&frp.bounds).map(|(Bounds{start,end},bounds)|{ Bounds::new(normalise_value(&(*start,*bounds)),normalise_value(&(*end,*bounds))) }); - normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map(|(overflow_bounds,bounds)| + normalised_overflow_bounds <- all(&frp.use_overflow_bounds,&frp.bounds).map( + |(overflow_bounds,bounds)| overflow_bounds.map(|Bounds{start,end}| Bounds::new(normalise_value(&(start,*bounds)),normalise_value(&(end,*bounds))) ) @@ -88,8 +89,8 @@ impl component::Frp for Frp { change_right_value <- base_frp.is_dragging_right_handle || base_frp.is_dragging_right_overflow; - drag_movement <- mouse.translation.gate(&base_frp.is_dragging_any); - drag_delta <- drag_movement.map2(&base_frp.track_max_width, |delta,width| + drag_movement <- mouse.translation.gate(&base_frp.is_dragging_any); + drag_delta <- drag_movement.map2(&base_frp.track_max_width, |delta,width| (delta.x + delta.y) / width ); @@ -113,12 +114,13 @@ impl component::Frp for Frp { should_clamp_with_overflow(range,bounds) ); new_value_absolute <- all(&frp.set_bounds,&any_update).map(|(bounds,Bounds{start,end})| - Bounds::new(absolute_value(&(*bounds,*start)),absolute_value(&(*bounds,*end))).sorted() + Bounds::new( + absolute_value(&(*bounds,*start)),absolute_value(&(*bounds,*end))).sorted() ); frp.source.value <+ new_value_absolute.gate(&is_in_bounds); } - // Init defaults; + // Init defaults frp.set_bounds(Bounds::new(0.0,1.0)); frp.use_overflow_bounds(None); frp.set_value(Bounds::new(0.25,0.75)); From 3bd44513835490ae6300923cbfd4188484eb674b Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 11:08:44 +0200 Subject: [PATCH 14/25] style: Code formatting. --- src/rust/ensogl/lib/components/src/shadow.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/shadow.rs b/src/rust/ensogl/lib/components/src/shadow.rs index 5bac6d549b..082f0473c9 100644 --- a/src/rust/ensogl/lib/components/src/shadow.rs +++ b/src/rust/ensogl/lib/components/src/shadow.rs @@ -116,12 +116,12 @@ pub struct ParametersFrp { pub fn frp_from_style(style:&StyleWatchFrp,path:impl Into) -> ParametersFrp { let path: style::Path = path.into(); ParametersFrp{ - base_color: style.get_color(&path), - fading: style.get_color(&path.sub("fading")), - size: style.get_number(&path.sub("size")), - spread: style.get_number(&path.sub("spread")), - exponent: style.get_number(&path.sub("exponent")), - offset_x: style.get_number(&path.sub("offset_x")), - offset_y: style.get_number(&path.sub("offset_y")) + base_color : style.get_color(&path), + fading : style.get_color(&path.sub("fading")), + size : style.get_number(&path.sub("size")), + spread : style.get_number(&path.sub("spread")), + exponent : style.get_number(&path.sub("exponent")), + offset_x : style.get_number(&path.sub("offset_x")), + offset_y : style.get_number(&path.sub("offset_y")) } } From 494953709c790808eeafee763c8bbbd8f5c28468 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 14:22:10 +0200 Subject: [PATCH 15/25] Update src/rust/ensogl/lib/components/src/selector/common/base_frp.rs Co-authored-by: Adam Obuchowicz --- src/rust/ensogl/lib/components/src/selector/common/base_frp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs b/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs index 71ee8011d1..3cfa3bdd45 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs @@ -16,7 +16,7 @@ use crate::shadow; // =========================== -// === Frp utility methods === +// === Frp Utility Methods === // =========================== /// Compute the slider width from the given shape size. For use in FRP, thus taking a reference. From 89c1718b30ca6405c1356d682f3d84ac78d2e7cc Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 14:23:35 +0200 Subject: [PATCH 16/25] style: Limit line length. --- .../src/selector/common/base_frp.rs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs b/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs index 3cfa3bdd45..8c4e8cb43e 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs @@ -57,16 +57,24 @@ pub struct BaseFrp { } impl BaseFrp { - pub fn new(model:&Model, style:&StyleWatchFrp, network:&Network, size:frp::Stream, mouse:&Mouse) -> BaseFrp { + pub fn new + (model:&Model, style:&StyleWatchFrp, network:&Network, size:frp::Stream, mouse:&Mouse) + -> BaseFrp { let shadow = shadow::frp_from_style(style,theme::shadow); let text_size = style.get_number(theme::text::size); - let is_dragging_left_overflow = common::shape_is_dragged(network,&model.left_overflow.events,mouse); - let is_dragging_right_overflow = common::shape_is_dragged(network,&model.right_overflow.events,mouse); - let is_dragging_track = common::shape_is_dragged(network,&model.track.events, mouse); - let is_dragging_background = common::shape_is_dragged(network,&model.background.events,mouse); - let is_dragging_left_handle = common::shape_is_dragged(network,&model.track_handle_left.events,mouse); - let is_dragging_right_handle = common::shape_is_dragged(network,&model.track_handle_right.events,mouse); + let is_dragging_left_overflow = common::shape_is_dragged( + network,&model.left_overflow.events,mouse); + let is_dragging_right_overflow = common::shape_is_dragged( + network,&model.right_overflow.events,mouse); + let is_dragging_track = common::shape_is_dragged( + network,&model.track.events, mouse); + let is_dragging_background = common::shape_is_dragged( + network,&model.background.events,mouse); + let is_dragging_left_handle = common::shape_is_dragged( + network,&model.track_handle_left.events,mouse); + let is_dragging_right_handle = common::shape_is_dragged( + network,&model.track_handle_right.events,mouse); // Initialisation of components. Required for correct layout on startup. model.label_right.set_position_y(text_size.value()/2.0); From 4f1d71f107f42c18cb23601764c6f760a156cd42 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 15:26:54 +0200 Subject: [PATCH 17/25] test+refactor: Extend tests. --- .../lib/components/src/selector/common.rs | 65 +++++++++++++++++-- .../lib/components/src/selector/number.rs | 11 +++- .../lib/components/src/selector/range.rs | 3 - 3 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/common.rs index cd53bbcfc2..c4f6f715e3 100644 --- a/src/rust/ensogl/lib/components/src/selector/common.rs +++ b/src/rust/ensogl/lib/components/src/selector/common.rs @@ -137,15 +137,14 @@ pub fn shape_is_dragged /// Returns the position of a mouse down on a shape. The position is given relative to the origin /// of the shape position. pub fn relative_shape_click_position -(model:&Model, network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { - let model = model.clone_ref(); +(base_position:impl Fn() -> Vector2 + 'static, network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { frp::extend! { network mouse_down <- mouse.down.constant(()); over_shape <- bool(&shape.mouse_out,&shape.mouse_over); mouse_down_over_shape <- mouse_down.gate(&over_shape); background_click_positon <- mouse.position.sample(&mouse_down_over_shape); background_click_positon <- background_click_positon.map(move |pos| - pos - model.position().xy() + pos - base_position() ); } background_click_positon @@ -157,6 +156,9 @@ mod tests { use super::*; use float_eq::assert_float_eq; use std::f32::NAN; + use enso_frp::stream::ValueProvider; + use enso_frp::stream::EventEmitter; + use enso_frp::io::mouse::Button; #[test] fn test_normalise_value() { @@ -385,4 +387,59 @@ mod tests { test(Bounds::new(0.0,0.0),None,true); } -} \ No newline at end of file + + #[test] + fn test_shape_is_dragged() { + let network = frp::Network::new("TestNetwork"); + let mouse = frp::io::Mouse::default(); + let shape = ShapeViewEvents::default(); + + let is_dragged = shape_is_dragged(&network,&shape,&mouse); + let _watch = is_dragged.register_watch(); + + + // Default is false. + assert_eq!(is_dragged.value(),false); + + // Mouse down over shape activates dragging. + shape.mouse_over.emit(()); + mouse.down.emit(Button::from_code(0)); + assert_eq!(is_dragged.value(),true); + + // Release mouse stops dragging. + mouse.up.emit(Button::from_code(0)); + assert_eq!(is_dragged.value(),false); + + // Mouse down while not over shape does not activate dragging. + shape.mouse_out.emit(()); + mouse.down.emit(Button::from_code(0)); + assert_eq!(is_dragged.value(),false); + } + + #[test] + fn test_relative_shape_click_position() { + let network = frp::Network::new("TestNetwork"); + let mouse = frp::io::Mouse::default(); + let shape = ShapeViewEvents::default(); + + let base_position = || Vector2::new(-10.0,200.0); + let click_position = relative_shape_click_position(base_position, &network,&shape,&mouse); + let _watch = click_position.register_watch(); + + shape.mouse_over.emit(()); + mouse.position.emit(Vector2::new(-10.0,200.0)); + mouse.down.emit(Button::from_code(0)); + assert_float_eq!(click_position.value().x,0.0,ulps<=7); + assert_float_eq!(click_position.value().y,0.0,ulps<=7); + + mouse.position.emit(Vector2::new(0.0,0.0)); + mouse.down.emit(Button::from_code(0)); + assert_float_eq!(click_position.value().x,10.0,ulps<=7); + assert_float_eq!(click_position.value().y,-200.0,ulps<=7); + + mouse.position.emit(Vector2::new(400.0,0.5)); + mouse.down.emit(Button::from_code(0)); + assert_float_eq!(click_position.value().x,410.0,ulps<=7); + assert_float_eq!(click_position.value().y,-199.5,ulps<=7); + } +} diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index b977e45f51..3e1daf10ba 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -48,10 +48,15 @@ impl component::Frp for Frp { let track_shape_system = scene.shapes.shape_system(PhantomData::); track_shape_system.shape_system.set_pointer_events(false); + let madel_fn = model.clone_ref(); + let base_position = move || madel_fn.position().xy(); let background_click = relative_shape_click_position( - model,network,&model.background.events,mouse); - let track_click = relative_shape_click_position( - model,network,&model.track.events,mouse); + base_position,network,&model.background.events,mouse); + + let madel_fn = model.clone_ref(); + let base_position = move || madel_fn.position().xy(); + let track_click = relative_shape_click_position( + base_position,network,&model.track.events,mouse); frp::extend! { network diff --git a/src/rust/ensogl/lib/components/src/selector/range.rs b/src/rust/ensogl/lib/components/src/selector/range.rs index e3251de6a7..6b3cd3653f 100644 --- a/src/rust/ensogl/lib/components/src/selector/range.rs +++ b/src/rust/ensogl/lib/components/src/selector/range.rs @@ -1,7 +1,6 @@ use crate::prelude::*; use enso_frp as frp; -use ensogl_core::Animation; use ensogl_core::application::Application; use ensogl_core::display::shape::*; use ensogl_core::display::shape::StyleWatchFrp; @@ -42,8 +41,6 @@ impl component::Frp for Frp { let scene = app.display.scene(); let mouse = &scene.mouse.frp; - let _slider_animation = Animation::::new(network); - let base_frp = BaseFrp::new(model,style,network,frp.resize.clone().into(),mouse); model.use_track_handles(true); From d2982c1a46b49e05e862bca0005c28acc88b7ebf Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 4 May 2021 21:36:01 +0200 Subject: [PATCH 18/25] refactor: Fix clippy lints. --- src/rust/ensogl/lib/components/src/selector/common.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/common.rs index c4f6f715e3..76f286e01b 100644 --- a/src/rust/ensogl/lib/components/src/selector/common.rs +++ b/src/rust/ensogl/lib/components/src/selector/common.rs @@ -36,16 +36,16 @@ impl Bounds { } /// Return the `Bound` with the lower bound as `start` and the upper bound as `end`. - pub fn sorted(&self) -> Self { + pub fn sorted(self) -> Self { if self.start > self.end { Bounds{start:self.end,end:self.start} } else { - self.clone() + self } } /// Return the distance between start and end point. - pub fn width(&self) -> f32 { + pub fn width(self) -> f32 { (self.end - self.start) } } From fc688e1ffd98771fb952240b3de39f9509abd93b Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Wed, 5 May 2021 10:38:18 +0200 Subject: [PATCH 19/25] feat: Implement custom track colors. --- src/rust/ensogl/example/src/slider.rs | 5 ++++- .../ensogl/lib/components/src/selector/common/model.rs | 5 +++++ .../ensogl/lib/components/src/selector/common/shape.rs | 7 ++++--- src/rust/ensogl/lib/components/src/selector/number.rs | 7 +++++++ src/rust/ensogl/lib/components/src/selector/range.rs | 7 +++++++ 5 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs index ddd6338c4b..d660cb1f36 100644 --- a/src/rust/ensogl/example/src/slider.rs +++ b/src/rust/ensogl/example/src/slider.rs @@ -4,10 +4,11 @@ use crate::prelude::*; use wasm_bindgen::prelude::*; use ensogl_core::application::Application; +use ensogl_core::data::color; use ensogl_core::display::object::ObjectOps; use ensogl_core::system::web; -use ensogl_gui_components::selector; use ensogl_gui_components::selector::Bounds; +use ensogl_gui_components::selector; use ensogl_text_msdf_sys::run_once_initialized; use ensogl_theme as theme; @@ -68,9 +69,11 @@ fn init(app:&Application) { let slider3 = make_range_picker(app); slider3.inner().set_position_y(-100.0); + slider3.inner().set_track_color(color::Rgba::new(0.0,0.80,0.80,1.0)); let slider4 = make_range_picker(app); slider4.inner().set_position_y(-200.0); slider4.inner().frp.use_overflow_bounds(Bounds::new(-2.0,3.0)); slider4.inner().frp.set_caption(Some("Caption".to_string())); + slider4.inner().set_track_color(color::Rgba::new(0.5,0.70,0.70,1.0)); } diff --git a/src/rust/ensogl/lib/components/src/selector/common/model.rs b/src/rust/ensogl/lib/components/src/selector/common/model.rs index 5ea8fdfd5f..b64e83bc4e 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/model.rs @@ -7,6 +7,7 @@ use crate::selector::common; use enso_frp as frp; use ensogl_core::application::Application; use ensogl_core::application; +use ensogl_core::data::color; use ensogl_core::display::shape::*; use ensogl_core::display; use ensogl_text as text; @@ -270,6 +271,10 @@ impl Model { self.background.corner_right.set(corner_roundness); self.track.corner_right.set(corner_roundness) } + + pub fn set_track_color(&self, color:color::Rgba) { + self.track.track_color.set(color.into()); + } } impl display::Object for Model { diff --git a/src/rust/ensogl/lib/components/src/selector/common/shape.rs b/src/rust/ensogl/lib/components/src/selector/common/shape.rs index 25ed1ff3d8..3458249909 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/common/shape.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use ensogl_core::data::color; use ensogl_core::display::shape::*; use ensogl_theme as theme; @@ -33,7 +34,7 @@ impl Background { let rect_left = rect_left.translate_x(-&width/4.0); let rect_right = Rect((&width/2.0,&height)).corners_radius(&corner_radius*corner_right); let rect_right = rect_right.translate_x(&width/4.0); - let rect_center = Rect((&corner_radius,&height)); + let rect_center = Rect((&corner_radius*2.0,&height)); let shape = (rect_left+rect_right+rect_center).into(); @@ -95,7 +96,7 @@ pub mod track { use super::*; ensogl_core::define_shape_system! { - (style:Style,left:f32,right:f32,corner_left:f32,corner_right:f32) { + (style:Style,left:f32,right:f32,corner_left:f32,corner_right:f32,track_color:Vector4) { let background = Background::new(&corner_left,&corner_right,style); let width = background.width; let height = background.height; @@ -107,7 +108,7 @@ pub mod track { let track = track.translate_x(-width/2.0); let track = track.intersection(&background.shape); - let track_color = style.get_color(theme::component::slider::track::color); + let track_color = Var::::from(track_color); let track = track.fill(track_color); track.into() } diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index 3e1daf10ba..2faa450367 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -1,9 +1,11 @@ use crate::prelude::*; use enso_frp as frp; +use ensogl_core::data::color; use ensogl_core::application::Application; use ensogl_core::display::shape::*; use ensogl_core::display::shape::StyleWatchFrp; +use ensogl_theme as theme; use crate::component; use crate::selector::common::Model; @@ -29,6 +31,7 @@ ensogl_core::define_endpoints! { set_caption(Option), set_left_corner_round(bool), set_right_corner_round(bool), + set_track_color(color::Rgba), } Output { value(f32), @@ -58,6 +61,8 @@ impl component::Frp for Frp { let track_click = relative_shape_click_position( base_position,network,&model.track.events,mouse); + let style_track_color = style.get_color(theme::component::slider::track::color); + frp::extend! { network // Rebind @@ -68,6 +73,7 @@ impl component::Frp for Frp { eval frp.set_caption((caption) model.set_caption_left(caption.clone())); eval frp.set_left_corner_round ((value) model.left_corner_round(*value)); eval frp.set_right_corner_round((value) model.right_corner_round(*value)); + eval frp.set_track_color((value) model.set_track_color(*value)); // Internal norm_value <- all2(&frp.value,&frp.bounds).map(normalise_value); @@ -123,5 +129,6 @@ impl component::Frp for Frp { frp.set_value(0.5); frp.set_left_corner_round(true); frp.set_right_corner_round(true); + frp.set_track_color(style_track_color.value()); } } diff --git a/src/rust/ensogl/lib/components/src/selector/range.rs b/src/rust/ensogl/lib/components/src/selector/range.rs index 6b3cd3653f..5b6e24f559 100644 --- a/src/rust/ensogl/lib/components/src/selector/range.rs +++ b/src/rust/ensogl/lib/components/src/selector/range.rs @@ -1,9 +1,11 @@ use crate::prelude::*; use enso_frp as frp; +use ensogl_core::data::color; use ensogl_core::application::Application; use ensogl_core::display::shape::*; use ensogl_core::display::shape::StyleWatchFrp; +use ensogl_theme as theme; use crate::selector::common::Model; use crate::component; @@ -27,6 +29,7 @@ ensogl_core::define_endpoints! { set_caption(Option), set_left_corner_round(bool), set_right_corner_round(bool), + set_track_color(color::Rgba), } Output { value(Bounds), @@ -45,6 +48,8 @@ impl component::Frp for Frp { model.use_track_handles(true); + let style_track_color = style.get_color(theme::component::slider::track::color); + frp::extend! { network // Rebind @@ -55,6 +60,7 @@ impl component::Frp for Frp { eval frp.set_caption((caption) model.set_caption_center(caption.clone())); eval frp.set_left_corner_round ((value) model.left_corner_round(*value)); eval frp.set_right_corner_round((value) model.right_corner_round(*value)); + eval frp.set_track_color((value) model.set_track_color(*value)); // Internal norm_value <- all2(&frp.value,&frp.bounds).map(|(Bounds{start,end},bounds)|{ @@ -123,6 +129,7 @@ impl component::Frp for Frp { frp.set_value(Bounds::new(0.25,0.75)); frp.set_left_corner_round(true); frp.set_right_corner_round(true); + frp.set_track_color(style_track_color.value()); } } From 18096bc04696a1524204cd4bf2e5b53a60648e47 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Wed, 5 May 2021 11:01:59 +0200 Subject: [PATCH 20/25] chore: Update changelog. --- CHANGELOG.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b92123a3a..dd7b133fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ #### Visual Environment -#### EnsoGL (rendering engine) +- [Components for picking numbers and ranges.][1524]. We now have some internal + re-usable UI components for selecting numbers or a range. Stay tuned for them + appearing in the IDE. +#### EnsoGL (rendering engine)
![Bug Fixes](/docs/assets/tags/bug_fixes.svg) @@ -21,6 +24,8 @@ If you're interested in the enhancements and fixes made to the Enso compiler, you can find their release notes [here](https://github.com/enso-org/enso/blob/main/RELEASES.md). +[1524]: https://github.com/enso-org/ide/pull/1524 +
# Enso 2.0.0-alpha.4 (2021-05-04) @@ -37,10 +42,7 @@ you can find their release notes arguments following the separator will be passed to the backend. - [Added `--verbose` parameter][1531]. If `--verbose` is given as command line argument, the IDE and the backend will produce more detailed logs. -- [Components for picking numbers and ranges.][1524]. We now have some internal - re-usable UI components for selecting numbers or a range. Stay tuned for them - appearing in the IDE. - +
![Bug Fixes](/docs/assets/tags/bug_fixes.svg) #### Visual Environment @@ -241,7 +243,6 @@ you can find their release notes [1447]: https://github.com/enso-org/ide/pull/1447 [1471]: https://github.com/enso-org/ide/pull/1471 [1511]: https://github.com/enso-org/ide/pull/1511 -[1524]: https://github.com/enso-org/ide/pull/1524
From 624622714d8fa4b9be9546f8b6cb2664baff8d8b Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Thu, 13 May 2021 11:57:40 +0200 Subject: [PATCH 21/25] style+refactor+docs: Apply review feedback. --- src/rust/ensogl/example/src/leak.rs | 6 +- src/rust/ensogl/lib/components/Cargo.toml | 2 +- .../ensogl/lib/components/src/component.rs | 8 +- .../ensogl/lib/components/src/selector.rs | 46 ++++- .../src/selector/{common.rs => bounds.rs} | 149 +++++------------ .../src/selector/decimal_aligned.rs | 107 ++++++++++++ .../selector/{common/base_frp.rs => frp.rs} | 27 +-- .../src/selector/{common => }/model.rs | 158 +++++++----------- .../lib/components/src/selector/number.rs | 17 +- .../lib/components/src/selector/range.rs | 16 +- .../src/selector/{common => }/shape.rs | 132 ++++++++++++++- 11 files changed, 411 insertions(+), 257 deletions(-) rename src/rust/ensogl/lib/components/src/selector/{common.rs => bounds.rs} (72%) create mode 100644 src/rust/ensogl/lib/components/src/selector/decimal_aligned.rs rename src/rust/ensogl/lib/components/src/selector/{common/base_frp.rs => frp.rs} (88%) rename src/rust/ensogl/lib/components/src/selector/{common => }/model.rs (62%) rename src/rust/ensogl/lib/components/src/selector/{common => }/shape.rs (56%) diff --git a/src/rust/ensogl/example/src/leak.rs b/src/rust/ensogl/example/src/leak.rs index add02e3f3b..73e7b3986d 100644 --- a/src/rust/ensogl/example/src/leak.rs +++ b/src/rust/ensogl/example/src/leak.rs @@ -1,10 +1,14 @@ //! `Leak` is a utility struct that prevents the wrapped value from being dropped when `Leak` is //! being dropped. This is achieved by passing the contained value to `std::mem::forget` in the -//! drop implementation iof `Leak`. Can bue used in examples to keep components alive for the whole +//! drop implementation of `Leak`. Can bue used for examples to keep components alive for the whole //! lifetime of the application. +// ============ +// === Leak === +// ============ + /// Wrapper that will prevent the wrapped value from being dropped. Instead, the value will be /// leaked when the `Leak` is dropped. #[derive(Debug)] diff --git a/src/rust/ensogl/lib/components/Cargo.toml b/src/rust/ensogl/lib/components/Cargo.toml index 6d70b71dd0..5d524019c2 100644 --- a/src/rust/ensogl/lib/components/Cargo.toml +++ b/src/rust/ensogl/lib/components/Cargo.toml @@ -19,4 +19,4 @@ ensogl-theme = { path = "../theme" } [dev-dependencies] wasm-bindgen-test = { version = "0.3.8" } -float_eq = "0.5" \ No newline at end of file +float_eq = "0.5" diff --git a/src/rust/ensogl/lib/components/src/component.rs b/src/rust/ensogl/lib/components/src/component.rs index e9572ec7df..a87eef8830 100644 --- a/src/rust/ensogl/lib/components/src/component.rs +++ b/src/rust/ensogl/lib/components/src/component.rs @@ -54,8 +54,8 @@ pub trait Frp : Default + CommandApi { #[derive(Clone,CloneRef,Debug)] pub struct Component { /// Public FRP api of the Component. - pub frp : Rc, - pub(crate) model : Rc, + pub frp : Rc, + model : Rc, /// Reference to the application the Component belongs to. Generally required for implementing /// `application::View` and initialising the `Mode`l and `Frp` and thus provided by the /// `Component`. @@ -69,9 +69,9 @@ impl> Component { let model = Rc::new(M::new(&app)); let frp = F::default(); let style = StyleWatchFrp::new(&app.display.scene().style_sheet); - frp.init(&app, &model,&style); + frp.init(&app,&model,&style); let frp = Rc::new(frp); - Self { model, frp, app } + Self {model,frp,app} } } diff --git a/src/rust/ensogl/lib/components/src/selector.rs b/src/rust/ensogl/lib/components/src/selector.rs index 179b6aa874..05e9cdccf2 100644 --- a/src/rust/ensogl/lib/components/src/selector.rs +++ b/src/rust/ensogl/lib/components/src/selector.rs @@ -1,20 +1,42 @@ -//! UI components for selecting numbers, and a range of numbers. -mod range; +//! UI components that allows picking a number or range through mouse interaction. We have a number +//! picker that allows to pick a number in a range and a range picker that allows to pick a range. +//! +//! Both share the same model (i.e., arrangement of shapes), but they have different logic about +//! using the features of the shapes making up the model and handling interactions. The base model +//! is implemented in `model.rs` and the logic for the number and range picker are placed in +//! `number.rs` and `range.rs` respectively. The rest of the sub-modules contain utilities. +//! +//! The only things exposed form this module are the `NumberPicker`, `NumberRangePicker`, their +//! FRPs and the `Bounds` struct. + +mod bounds; +mod decimal_aligned; +mod frp; +mod model; mod number; -mod common; +mod range; +mod shape; use ensogl_core::application; use ensogl_core::application::Application; -pub use common::Bounds; +pub use bounds::Bounds; +use model::*; +use frp::*; + // ===================== // === Number Picker === // ===================== -/// UI component for selecting a number. -pub type NumberPicker = crate::component::Component; +/// UI component for selecting a number. Looks like a rounded node that has the selected value as +/// text representation in the center. The background shows the selected value visually in the +/// range that the value can be picked from (e.g., 0..255) by by filling in the proportion of the +/// background that corresponds to the value relative in the range, for example, 0.0 would be not +/// filled in, 128.0 would be about halfway filled in, and 128.0 would be completely filled in. +/// The value can be changed by clicking and dragging on the shape. +pub type NumberPicker = crate::component::Component; impl application::View for NumberPicker { fn label() -> &'static str { "NumberPicker" } @@ -28,8 +50,16 @@ impl application::View for NumberPicker { // === Number Range Picker === // =========================== -/// UI component for selecting a number. -pub type NumberRangePicker = crate::component::Component; +/// UI component for selecting a ranger. Looks like a rounded node that shows a textual +/// representation of the range bounds at the left and right end of the shape. The background shows +/// the selected value visually in the by displaying a track within the range of permissible values +/// that the range bounds can be selected from. For example, if the allowed range is 0.0..1.0 +/// a selected range of 0..0.5 would show the track covering the left half of the background, a +/// selected range of 0.25..0.75 would show the track centered on the background, and 0.5..1.0 +/// would show the track covering the right half of the background. The selected range can be +/// changed by clicking and dragging the track, which changes the whole range, but preserves the +/// width, or the individual edges of the track which changes just the respective end of the range. +pub type NumberRangePicker = crate::component::Component; impl application::View for NumberRangePicker { fn label() -> &'static str { "RangePicker" } diff --git a/src/rust/ensogl/lib/components/src/selector/common.rs b/src/rust/ensogl/lib/components/src/selector/bounds.rs similarity index 72% rename from src/rust/ensogl/lib/components/src/selector/common.rs rename to src/rust/ensogl/lib/components/src/selector/bounds.rs index 76f286e01b..8eda4da2cf 100644 --- a/src/rust/ensogl/lib/components/src/selector/common.rs +++ b/src/rust/ensogl/lib/components/src/selector/bounds.rs @@ -1,17 +1,14 @@ -//! Common functionality for both the Number and Range selector. -use crate::prelude::*; - -use enso_frp as frp; -use enso_frp::Network; -use ensogl_core::frp::io::Mouse; -use ensogl_core::gui::component::ShapeViewEvents; +//! Bounds represent an interval with inclusive ends. They are used to indicates the lowest and +//! highest value that can be selected in a selection component. +//! +//! Note: this is used instead of `Range`, as `Range` cannot easily be used in our FRP because it +//! does not implement `Default`. -pub mod base_frp; -pub mod model; -pub mod shape; +use crate::prelude::*; -pub use base_frp::*; -pub use model::*; +use core::convert::From; +use core::option::Option; +use core::option::Option::Some; @@ -51,12 +48,17 @@ impl Bounds { } impl From<(f32,f32)> for Bounds { - fn from((start,end): (f32, f32)) -> Self { + fn from((start,end): (f32,f32)) -> Self { Bounds{start,end} } } /// Frp utility method to normalise the given value to the given Bounds. +/// +/// Example usage: +/// ```ignore +/// normalised <- all2(&value,&bounds).map(normalise_value); +/// ```` pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 { let width = bounds.width(); if width == 0.0 { return 0.0 } @@ -65,6 +67,11 @@ pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 { /// Frp utility method to compute the absolute value from a normalised value. /// Inverse of `normalise_value`. +/// +/// Example usage: +/// ```ignore +/// value <- all(&bounds,&normalised).map(absolute_value); +/// ```` pub fn absolute_value((bounds,normalised_value):&(Bounds,f32)) -> f32 { ((normalised_value * bounds.width()) + bounds.start) } @@ -92,6 +99,11 @@ pub fn bounds_in_bounds(bounds_inner:Bounds, bounds_outer:Bounds) -> bool { /// Clamp `value` to the `overflow_bounds`, or to [0, 1] if no bounds are given. /// For use in FRP `map` method, thus taking references. +/// +/// Example usage: +/// ```ignore +/// clamped <- value_update.map2(&normalised_overflow_bounds,clamp_with_overflow); +/// ``` #[allow(clippy::trivially_copy_pass_by_ref)] pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option) -> f32 { if let Some(overflow_bounds) = overflow_bounds{ @@ -103,6 +115,11 @@ pub fn clamp_with_overflow(value:&f32, overflow_bounds:&Option) -> f32 { /// Indicates whether the `bounds` would be clamped when given to `clamp_with_overflow`. /// For use in FRP `map` method, thus taking references. +/// +/// Example usage: +/// ```ignore +/// is_in_bounds <- bounds_update.map2(&normalised_overflow_bounds,should_clamp_with_overflow); +/// ``` #[allow(clippy::trivially_copy_pass_by_ref)] pub fn should_clamp_with_overflow(bounds:&Bounds, overflow_bounds:&Option) -> bool { if let Some(overflow_bounds) = overflow_bounds { @@ -114,51 +131,16 @@ pub fn should_clamp_with_overflow(bounds:&Bounds, overflow_bounds:&Option frp::Stream { - frp::extend! { network - mouse_up <- mouse.up.constant(()); - mouse_down <- mouse.down.constant(()); - over_shape <- bool(&shape.mouse_out,&shape.mouse_over); - mouse_down_over_shape <- mouse_down.gate(&over_shape); - is_dragging_shape <- bool(&mouse_up,&mouse_down_over_shape); - } - is_dragging_shape -} - -/// Returns the position of a mouse down on a shape. The position is given relative to the origin -/// of the shape position. -pub fn relative_shape_click_position -(base_position:impl Fn() -> Vector2 + 'static, network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> frp::Stream { - frp::extend! { network - mouse_down <- mouse.down.constant(()); - over_shape <- bool(&shape.mouse_out,&shape.mouse_over); - mouse_down_over_shape <- mouse_down.gate(&over_shape); - background_click_positon <- mouse.position.sample(&mouse_down_over_shape); - background_click_positon <- background_click_positon.map(move |pos| - pos - base_position() - ); - } - background_click_positon -} - +// ============= +// === Tests === +// ============= #[cfg(test)] mod tests { use super::*; + use float_eq::assert_float_eq; use std::f32::NAN; - use enso_frp::stream::ValueProvider; - use enso_frp::stream::EventEmitter; - use enso_frp::io::mouse::Button; #[test] fn test_normalise_value() { @@ -176,8 +158,8 @@ mod tests { test(0.0,1.0,0.5,0.5); test(0.0,1.0,0.6,0.6); test(0.0,1.0,0.7,0.7); - test(0.0,1.0,0.7,0.7); - test(0.0,1.0,0.7,0.7); + test(0.0,1.0,0.8,0.8); + test(0.0,1.0,0.9,0.9); test(0.0,1.0,1.0,1.0); test(0.0,1.0,-2.0,-2.0); @@ -226,8 +208,8 @@ mod tests { test(0.0,1.0,0.5,0.5); test(0.0,1.0,0.6,0.6); test(0.0,1.0,0.7,0.7); - test(0.0,1.0,0.7,0.7); - test(0.0,1.0,0.7,0.7); + test(0.0,1.0,0.8,0.8); + test(0.0,1.0,0.9,0.9); test(0.0,1.0,1.0,1.0); test(0.0,1.0,-2.0,-2.0); @@ -387,59 +369,4 @@ mod tests { test(Bounds::new(0.0,0.0),None,true); } - - #[test] - fn test_shape_is_dragged() { - let network = frp::Network::new("TestNetwork"); - let mouse = frp::io::Mouse::default(); - let shape = ShapeViewEvents::default(); - - let is_dragged = shape_is_dragged(&network,&shape,&mouse); - let _watch = is_dragged.register_watch(); - - - // Default is false. - assert_eq!(is_dragged.value(),false); - - // Mouse down over shape activates dragging. - shape.mouse_over.emit(()); - mouse.down.emit(Button::from_code(0)); - assert_eq!(is_dragged.value(),true); - - // Release mouse stops dragging. - mouse.up.emit(Button::from_code(0)); - assert_eq!(is_dragged.value(),false); - - // Mouse down while not over shape does not activate dragging. - shape.mouse_out.emit(()); - mouse.down.emit(Button::from_code(0)); - assert_eq!(is_dragged.value(),false); - } - - #[test] - fn test_relative_shape_click_position() { - let network = frp::Network::new("TestNetwork"); - let mouse = frp::io::Mouse::default(); - let shape = ShapeViewEvents::default(); - - let base_position = || Vector2::new(-10.0,200.0); - let click_position = relative_shape_click_position(base_position, &network,&shape,&mouse); - let _watch = click_position.register_watch(); - - shape.mouse_over.emit(()); - mouse.position.emit(Vector2::new(-10.0,200.0)); - mouse.down.emit(Button::from_code(0)); - assert_float_eq!(click_position.value().x,0.0,ulps<=7); - assert_float_eq!(click_position.value().y,0.0,ulps<=7); - - mouse.position.emit(Vector2::new(0.0,0.0)); - mouse.down.emit(Button::from_code(0)); - assert_float_eq!(click_position.value().x,10.0,ulps<=7); - assert_float_eq!(click_position.value().y,-200.0,ulps<=7); - - mouse.position.emit(Vector2::new(400.0,0.5)); - mouse.down.emit(Button::from_code(0)); - assert_float_eq!(click_position.value().x,410.0,ulps<=7); - assert_float_eq!(click_position.value().y,-199.5,ulps<=7); - } } diff --git a/src/rust/ensogl/lib/components/src/selector/decimal_aligned.rs b/src/rust/ensogl/lib/components/src/selector/decimal_aligned.rs new file mode 100644 index 0000000000..1b06f3e295 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/selector/decimal_aligned.rs @@ -0,0 +1,107 @@ +//! Utility wrapper for a text field representing a float. Centers the string representation of the +//! float on the decimal separator. This is a very bare bones implementation, thus not exposed as +//! a public utility. + +use crate::prelude::*; + +use crate::component; + +use enso_frp as frp; +use ensogl_core::application::Application; +use ensogl_core::application; +use ensogl_core::display::object::ObjectOps; +use ensogl_core::display::shape::StyleWatchFrp; +use ensogl_core::display; +use ensogl_text as text; + + + +// ================= +// === Constants === +// ================= + +const LABEL_OFFSET : f32 = 13.0; + + + +// ============ +// === FRP === +// ============ + +ensogl_core::define_endpoints! { + Input { + set_content(f32), + } + Output {} +} + + + +// ============== +// === Model === +// ============== + +#[derive(Clone,CloneRef,Debug)] +pub struct Model { + /// Root object. Required as the rendered text label will have an offset relative to the + /// base position of the root, depending on the position of the decimal separator. + root : display::object::Instance, + /// Label containing the text to display. This is the label that will be shown. + label_full : text::Area, + /// This label contains the text to the left of the decimal. This is here, so we can get + /// information about the text width of this portion of the label. This label will + /// not appear in the UI. + label_left : text::Area, +} + +impl component::Model for Model { + fn new(app:&Application) -> Self { + let logger = Logger::new("DecimalAlignedLabel"); + let root = display::object::Instance::new(&logger); + let label_full = app.new_view::(); + let label_left = app.new_view::(); + + label_full.remove_from_scene_layer_DEPRECATED(&app.display.scene().layers.main); + label_full.add_to_scene_layer_DEPRECATED(&app.display.scene().layers.label); + + root.add_child(&label_full); + root.add_child(&label_left); + + Self{root,label_full,label_left} + } +} + +impl component::Frp for Frp { + fn init(&self, _app:&Application, model:&Model, _style:&StyleWatchFrp) { + let frp = &self; + let network = &frp.network; + + frp::extend! { network + formatted <- frp.set_content.map(|value| format!("{:.2}", value)); + // FIXME: the next line is locale dependent as it is meant to split on the decimal + // separator, which might be different from "." in some locales. We need a way to get + // the current locale dependent decimal separator for this. + // See https://github.com/enso-org/ide/issues/1542 for progress on this. + left <- formatted.map(|s| s.split('.').next().map(|s| s.to_string())).unwrap(); + + model.label_left.set_content <+ left; + model.label_full.set_content <+ formatted; + + eval model.label_left.width((offset) + model.label_full.set_position_x(-offset-LABEL_OFFSET)); + } + } +} + +impl display::Object for Model { + fn display_object(&self) -> &display::object::Instance { self.root.display_object() } +} + +/// Decimal aligned text label that shows the text representation of a floating point number. +pub type FloatLabel = component::Component; + +impl application::View for FloatLabel { + fn label() -> &'static str { "FloatLabel" } + fn new(app:&Application) -> Self { FloatLabel::new(app) } + fn app(&self) -> &Application { &self.app } +} diff --git a/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs b/src/rust/ensogl/lib/components/src/selector/frp.rs similarity index 88% rename from src/rust/ensogl/lib/components/src/selector/common/base_frp.rs rename to src/rust/ensogl/lib/components/src/selector/frp.rs index 8c4e8cb43e..ce779d4776 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/base_frp.rs +++ b/src/rust/ensogl/lib/components/src/selector/frp.rs @@ -9,10 +9,11 @@ use ensogl_core::display::object::ObjectOps; use ensogl_core::display::shape::StyleWatchFrp; use ensogl_theme as theme; -use crate::selector::common; -use crate::selector::common::Model; use crate::shadow; +use super::model::Model; +use super::shape::shape_is_dragged; + // =========================== @@ -30,12 +31,12 @@ fn slider_area_width(size:&Vector2) -> f32 { // =============== -// === BaseFrp === +// === Frp === // =============== /// Frp endpoints provided for general information about mouse interactions and shape properties /// of the `common::Model`. -pub struct BaseFrp { +pub struct Frp { /// Current maximum extent of the track in scene coordinate space. pub track_max_width : frp::Stream, /// Indicates whether there is an ongoing dragging action from the left overflow shape. @@ -56,24 +57,24 @@ pub struct BaseFrp { pub is_dragging_any : frp::Stream, } -impl BaseFrp { +impl Frp { pub fn new (model:&Model, style:&StyleWatchFrp, network:&Network, size:frp::Stream, mouse:&Mouse) - -> BaseFrp { + -> Frp { let shadow = shadow::frp_from_style(style,theme::shadow); let text_size = style.get_number(theme::text::size); - let is_dragging_left_overflow = common::shape_is_dragged( + let is_dragging_left_overflow = shape_is_dragged( network,&model.left_overflow.events,mouse); - let is_dragging_right_overflow = common::shape_is_dragged( + let is_dragging_right_overflow = shape_is_dragged( network,&model.right_overflow.events,mouse); - let is_dragging_track = common::shape_is_dragged( + let is_dragging_track = shape_is_dragged( network,&model.track.events, mouse); - let is_dragging_background = common::shape_is_dragged( + let is_dragging_background = shape_is_dragged( network,&model.background.events,mouse); - let is_dragging_left_handle = common::shape_is_dragged( + let is_dragging_left_handle = shape_is_dragged( network,&model.track_handle_left.events,mouse); - let is_dragging_right_handle = common::shape_is_dragged( + let is_dragging_right_handle = shape_is_dragged( network,&model.track_handle_right.events,mouse); // Initialisation of components. Required for correct layout on startup. @@ -118,7 +119,7 @@ impl BaseFrp { ); } - BaseFrp {track_max_width,is_dragging_left_overflow,is_dragging_right_overflow, + Frp {track_max_width,is_dragging_left_overflow,is_dragging_right_overflow, is_dragging_track,is_dragging_background,is_dragging_left_handle, is_dragging_right_handle,is_dragging_any} } diff --git a/src/rust/ensogl/lib/components/src/selector/common/model.rs b/src/rust/ensogl/lib/components/src/selector/model.rs similarity index 62% rename from src/rust/ensogl/lib/components/src/selector/common/model.rs rename to src/rust/ensogl/lib/components/src/selector/model.rs index b64e83bc4e..42496e3433 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/model.rs @@ -1,102 +1,27 @@ -//! Base model for the number and range selector components. -use crate::prelude::*; +//! Base model for the number and range selector components. Contains all functionality needed by +//! both selectors and can be configured to suit the needs of both. -use crate::component; -use crate::selector::common; +use crate::prelude::*; -use enso_frp as frp; use ensogl_core::application::Application; -use ensogl_core::application; use ensogl_core::data::color; -use ensogl_core::display::shape::*; use ensogl_core::display; +use ensogl_core::display::shape::*; use ensogl_text as text; -use super::shape::*; - -const LABEL_OFFSET : f32 = 13.0; - - - -// ============================================== -// === Utilities - Decimal Aligned Text Field === -// ============================================== - -/// Utility wrapper for a text field containing a float. Centers the string representation of the -/// float on the decimal separator. -mod decimal_aligned { - use super::*; - - ensogl_core::define_endpoints! { - Input { - set_content(f32), - } - Output {} - } - - impl component::Frp for Frp { - fn init(&self, app: &Application, model: &Model, _style: &StyleWatchFrp) { - let frp = &self; - let network = &frp.network; - let _scene = app.display.scene(); - - frp::extend! { network - formatted <- frp.set_content.map(|value| format!("{:.2}", value)); - // FIXME: the next line is locale dependent. We need a way to get the current locale - // dependent decimal separator for this. - // See https://github.com/enso-org/ide/issues/1542 for progress on this. - left <- formatted.map(|s| s.split('.').next().map(|s| s.to_string())).unwrap(); - - model.label_left.set_content <+ left; - model.label_full.set_content <+ formatted; - - eval model.label_left.width((offset) model.label_full.set_position_x(-offset-LABEL_OFFSET)); - } - } - } - - #[derive(Clone,CloneRef,Debug)] - pub struct Model { - /// Root object. Required as the rendered text label will have an offset relative to the - /// base position of the root, depending on the position of the decimal separator. - root : display::object::Instance, - /// Label containing the text to display. This is the label that will be shown. - label_full : text::Area, - /// This label contains the text to the left of the decimal. This is here, so we can get - /// information about the text width of this portion of the label. This label will - /// not appear in the UI. - label_left : text::Area, - } - - impl component::Model for Model { - fn new(app:&Application) -> Self { - let logger = Logger::new("DecimalAlignedLabel"); - let root = display::object::Instance::new(&logger); - let label_full = app.new_view::(); - let label_left = app.new_view::(); - - label_full.remove_from_scene_layer_DEPRECATED(&app.display.scene().layers.main); - label_full.add_to_scene_layer_DEPRECATED(&app.display.scene().layers.label); +use crate::component; - root.add_child(&label_full); - root.add_child(&label_left); +use super::Bounds; +use super::shape::*; +use super::decimal_aligned::FloatLabel; - Self{root,label_full,label_left} - } - } - impl display::Object for Model { - fn display_object(&self) -> &display::object::Instance { self.label_full.display_object() } - } - pub type FloatLabel = crate::component::Component; +// ================= +// === Constants === +// ================= - impl application::View for FloatLabel { - fn label() -> &'static str { "DecimalAlignedLabel" } - fn new(app:&Application) -> Self { FloatLabel::new(app) } - fn app(&self) -> &Application { &self.app } - } -} +const LABEL_OFFSET : f32 = 13.0; @@ -106,17 +31,38 @@ mod decimal_aligned { #[derive(Clone,CloneRef,Debug)] pub struct Model { + /// Background shape that the other UI elements are placed on. pub background : background::View, + /// Visual element that indicates where in the available range the selected number or track is + /// located. Looks like a colored in bit of the background. pub track : track::View, + /// Invisible UI element that enables mouse interaction with the left end of the track. Will + /// always be placed on the left edge of the track. pub track_handle_left : io_rect::View, + /// Invisible UI element that enables mouse interaction with the right end of the track. Will + /// always be placed on the right edge of the track. pub track_handle_right : io_rect::View, + /// Icon that can be used out of range values. The left overflow is placed on the left side + /// of the shape and looks like an arrow/triangle pointing left. pub left_overflow : left_overflow::View, + /// Icon that can be used out of range values. The left overflow is placed on the right side + /// of the shape and looks like an arrow/triangle pointing right. pub right_overflow : right_overflow::View, - pub label : decimal_aligned::FloatLabel, + /// A label that is centered on the background and can be set to show a floating point value + /// that is centered on the decimal label. + pub label : FloatLabel, + /// A label that is aligned to the left edge of the background pub label_left : text::Area, + /// A label that is aligned to the right edge of the background pub label_right : text::Area, + /// A label that is left aligned on the background. Meant to contain a caption describing the + /// value that is selected. For example "Alpha", "Red", or "Size". pub caption_left : text::Area, + /// A label that is centered on the background. Meant to contain a caption describing the + /// range that is selected. For example "Allowed Size", or "Valid Price". pub caption_center : text::Area, + /// Shape root that all other elements are parented to. Should be used to place the shapes as + /// a group. pub root : display::object::Instance, } @@ -124,7 +70,7 @@ impl component::Model for Model { fn new(app: &Application) -> Self { let logger = Logger::new("selector::common::Model"); let root = display::object::Instance::new(&logger); - let label = app.new_view::(); + let label = app.new_view::(); let label_left = app.new_view::(); let label_right = app.new_view::(); let caption_center = app.new_view::(); @@ -136,12 +82,12 @@ impl component::Model for Model { let left_overflow = left_overflow::View::new(&logger); let right_overflow = right_overflow::View::new(&logger); - let app = app.clone_ref(); - let scene = app.display.scene(); - scene.layers.add_shapes_order_dependency::(); - scene.layers.add_shapes_order_dependency::(); - scene.layers.add_shapes_order_dependency::(); - scene.layers.add_shapes_order_dependency::(); + let app = app.clone_ref(); + let scene = app.display.scene(); + scene.layers.add_shapes_order_dependency::(); + scene.layers.add_shapes_order_dependency::(); + scene.layers.add_shapes_order_dependency::(); + scene.layers.add_shapes_order_dependency::(); root.add_child(&label); root.add_child(&label_left); @@ -167,6 +113,8 @@ impl component::Model for Model { } impl Model { + /// Set the size of the overall shape, taking into account the extra padding required to + /// render the shadow. pub fn set_size(&self, size:Vector2, shadow_padding:Vector2) { let padded_size = size + shadow_padding; self.background.size.set(padded_size); @@ -190,15 +138,20 @@ impl Model { self.track_handle_right.size.set(track_handle_size); } + /// Update the position of the captions based on the size of the shape and the text size. Takes + ///arguments in the way they are provided in the FRP network (as reference to a tuple) to make + ///usage more ergonomic on the call-site. pub fn update_caption_position(&self, (size,text_size):&(Vector2,f32)) { - let left_padding = LABEL_OFFSET; + let left_padding = LABEL_OFFSET; let overflow_icon_size = size.y / 2.0; - let caption_offset = size.x / 2.0 - overflow_icon_size - left_padding; + let caption_offset = size.x / 2.0 - overflow_icon_size - left_padding; self.caption_left.set_position_x(-caption_offset); self.caption_left.set_position_y(text_size / 2.0); self.caption_center.set_position_y(text_size / 2.0); } + /// Set whether to allow interactions with the edges of the track shape. If this is set to + /// `false`, both `track_handle_left` and `track_handle_right` are removed from the component. pub fn use_track_handles(&self, value:bool) { if value { self.track.add_child(&self.track_handle_left); @@ -209,12 +162,18 @@ impl Model { } } + /// Set the track to cover the area from zero up to the given value. The value indicates the + /// width of the background that should be covered in the range 0..1. pub fn set_background_value(&self, value:f32) { self.track.left.set(0.0); self.track.right.set(value); } - pub fn set_background_range(&self, value:common::Bounds, size:Vector2) { + /// Set the track to cover the area indicated by the `value` Bounds that are passed. The value + /// indicates location aon the background in the range 0..1 (were 0 is the left edge and 1 is + /// the right edge). To do the proper layout of the track handles this method also needs to be + /// passed the size of the shape. + pub fn set_background_range(&self, value:Bounds, size:Vector2) { self.track.left.set(value.start); self.track.right.set(value.end); @@ -222,14 +181,17 @@ impl Model { self.track_handle_right.set_position_x(value.end * size.x - size.x / 2.0); } + /// Set the label in the center of the background to show the given numeric value. pub fn set_center_label_content(&self, value:f32) { self.label.frp.set_content.emit(value) } + /// Set the label at the left edge of the background to show the given numeric value. pub fn set_left_label_content(&self, value:f32) { self.label_left.frp.set_content.emit(format!("{:.2}", value)) } + /// Set the label at the right edge of the background to show the given numeric value. pub fn set_right_label_content(&self, value:f32) { self.label_right.frp.set_content.emit(format!("{:.2}", value)) } diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index 2faa450367..352014bd06 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -1,3 +1,4 @@ +///! Frp of the number selector. use crate::prelude::*; use enso_frp as frp; @@ -8,12 +9,14 @@ use ensogl_core::display::shape::StyleWatchFrp; use ensogl_theme as theme; use crate::component; -use crate::selector::common::Model; -use crate::selector::common::base_frp::BaseFrp; -use crate::selector::common::relative_shape_click_position; - -use super::common::shape::*; -use super::common::*; +use crate::selector::shape::*; +use super::Bounds; +use super::bounds::absolute_value; +use super::bounds::clamp_with_overflow; +use super::bounds::normalise_value; +use super::bounds::position_to_normalised_value; +use super::shape::relative_shape_click_position; +use super::model::Model; @@ -46,7 +49,7 @@ impl component::Frp for Frp { let scene = app.display.scene(); let mouse = &scene.mouse.frp; - let base_frp = BaseFrp::new(model,style,network,frp.resize.clone().into(),mouse); + let base_frp = super::Frp::new(model,style,network,frp.resize.clone().into(),mouse); let track_shape_system = scene.shapes.shape_system(PhantomData::); track_shape_system.shape_system.set_pointer_events(false); diff --git a/src/rust/ensogl/lib/components/src/selector/range.rs b/src/rust/ensogl/lib/components/src/selector/range.rs index 5b6e24f559..f2ab0f4d1c 100644 --- a/src/rust/ensogl/lib/components/src/selector/range.rs +++ b/src/rust/ensogl/lib/components/src/selector/range.rs @@ -1,3 +1,4 @@ +///! Frp of the range selector. use crate::prelude::*; use enso_frp as frp; @@ -7,12 +8,13 @@ use ensogl_core::display::shape::*; use ensogl_core::display::shape::StyleWatchFrp; use ensogl_theme as theme; -use crate::selector::common::Model; use crate::component; -use crate::selector::common::base_frp::BaseFrp; -use super::common::*; -use super::common::Bounds; +use super::Model; +use super::Bounds; +use super::bounds::absolute_value; +use super::bounds::normalise_value; +use super::bounds::should_clamp_with_overflow; @@ -44,7 +46,7 @@ impl component::Frp for Frp { let scene = app.display.scene(); let mouse = &scene.mouse.frp; - let base_frp = BaseFrp::new(model,style,network,frp.resize.clone().into(),mouse); + let base_frp = super::Frp::new(model, style, network, frp.resize.clone().into(), mouse); model.use_track_handles(true); @@ -113,9 +115,7 @@ impl component::Frp for Frp { ); any_update <- any3(¢er_update,&left_update,&right_update); - is_in_bounds <- any_update.map2(&normalised_overflow_bounds, |range,bounds| - should_clamp_with_overflow(range,bounds) - ); + is_in_bounds <- any_update.map2(&normalised_overflow_bounds,should_clamp_with_overflow); new_value_absolute <- all(&frp.set_bounds,&any_update).map(|(bounds,Bounds{start,end})| Bounds::new( absolute_value(&(*bounds,*start)),absolute_value(&(*bounds,*end))).sorted() diff --git a/src/rust/ensogl/lib/components/src/selector/common/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs similarity index 56% rename from src/rust/ensogl/lib/components/src/selector/common/shape.rs rename to src/rust/ensogl/lib/components/src/selector/shape.rs index 3458249909..bc6e20ba78 100644 --- a/src/rust/ensogl/lib/components/src/selector/common/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -1,3 +1,4 @@ +//! This module contains the shapes and shape related functionality required. use crate::prelude::*; use ensogl_core::data::color; @@ -118,18 +119,18 @@ pub mod track { // ================ -// === OverFlow === +// === Overflow === // ================ /// Utility struct that contains the overflow shape, and some metadata that can be used to place and /// align it. -struct OverFlowShape { +struct OverflowShape { pub width : Var, pub height : Var, pub shape : AnyShape } -impl OverFlowShape { +impl OverflowShape { fn new(style:&StyleWatch) -> Self { let sprite_width : Var = "input_size.x".into(); let sprite_height : Var = "input_size.y".into(); @@ -144,7 +145,7 @@ impl OverFlowShape { let hover_area = hover_area.fill(HOVER_COLOR); let shape = (shape + hover_area).into(); - OverFlowShape{shape,width,height} + OverflowShape {shape,width,height} } } @@ -155,7 +156,7 @@ pub mod left_overflow { ensogl_core::define_shape_system! { (style:Style) { - let overflow_shape = OverFlowShape::new(style); + let overflow_shape = OverflowShape::new(style); let shape = overflow_shape.shape.rotate(-90.0_f32.to_radians().radians()); shape.into() } @@ -169,9 +170,128 @@ pub mod right_overflow { ensogl_core::define_shape_system! { (style:Style) { - let overflow_shape = OverFlowShape::new(style); + let overflow_shape = OverflowShape::new(style); let shape = overflow_shape.shape.rotate(90.0_f32.to_radians().radians()); shape.into() } } } + + + +// ======================= +// === Shape Utilities === +// ======================= + +use enso_frp; +use enso_frp::Network; +use ensogl_core::frp::io::Mouse; +use ensogl_core::gui::component::ShapeViewEvents; + +pub use super::frp::*; +pub use super::model::*; + +/// Return whether a dragging action has been started from the shape passed to this function. A +/// dragging action is started by a mouse down on the shape, followed by a movement of the mouse. +/// Dragging is ended by a mouse up. +pub fn shape_is_dragged +(network:&Network, shape:&ShapeViewEvents, mouse:&Mouse) -> enso_frp::Stream { + enso_frp::extend! { network + mouse_up <- mouse.up.constant(()); + mouse_down <- mouse.down.constant(()); + over_shape <- bool(&shape.mouse_out,&shape.mouse_over); + mouse_down_over_shape <- mouse_down.gate(&over_shape); + is_dragging_shape <- bool(&mouse_up,&mouse_down_over_shape); + } + is_dragging_shape +} + +/// Returns the position of a mouse down on a shape. The position is given relative to the origin +/// of the shape position. +pub fn relative_shape_click_position( + base_position:impl Fn() -> Vector2 + 'static, + network:&Network, + shape:&ShapeViewEvents, + mouse:&Mouse) -> enso_frp::Stream { + enso_frp::extend! { network + mouse_down <- mouse.down.constant(()); + over_shape <- bool(&shape.mouse_out,&shape.mouse_over); + mouse_down_over_shape <- mouse_down.gate(&over_shape); + background_click_positon <- mouse.position.sample(&mouse_down_over_shape); + background_click_positon <- background_click_positon.map(move |pos| + pos - base_position() + ); + } + background_click_positon +} + + + +// ============= +// === Tests === +// ============= + +#[cfg(test)] +mod tests { + use super::*; + + use enso_frp::io::mouse::Button; + use enso_frp::stream::EventEmitter; + use enso_frp::stream::ValueProvider; + use float_eq::assert_float_eq; + + #[test] + fn test_shape_is_dragged() { + let network = enso_frp::Network::new("TestNetwork"); + let mouse = enso_frp::io::Mouse::default(); + let shape = ShapeViewEvents::default(); + + let is_dragged = shape_is_dragged(&network,&shape,&mouse); + let _watch = is_dragged.register_watch(); + + + // Default is false. + assert_eq!(is_dragged.value(),false); + + // Mouse down over shape activates dragging. + shape.mouse_over.emit(()); + mouse.down.emit(Button::from_code(0)); + assert_eq!(is_dragged.value(),true); + + // Release mouse stops dragging. + mouse.up.emit(Button::from_code(0)); + assert_eq!(is_dragged.value(),false); + + // Mouse down while not over shape does not activate dragging. + shape.mouse_out.emit(()); + mouse.down.emit(Button::from_code(0)); + assert_eq!(is_dragged.value(),false); + } + + #[test] + fn test_relative_shape_click_position() { + let network = enso_frp::Network::new("TestNetwork"); + let mouse = enso_frp::io::Mouse::default(); + let shape = ShapeViewEvents::default(); + + let base_position = || Vector2::new(-10.0,200.0); + let click_position = relative_shape_click_position(base_position, &network,&shape,&mouse); + let _watch = click_position.register_watch(); + + shape.mouse_over.emit(()); + mouse.position.emit(Vector2::new(-10.0,200.0)); + mouse.down.emit(Button::from_code(0)); + assert_float_eq!(click_position.value().x,0.0,ulps<=7); + assert_float_eq!(click_position.value().y,0.0,ulps<=7); + + mouse.position.emit(Vector2::new(0.0,0.0)); + mouse.down.emit(Button::from_code(0)); + assert_float_eq!(click_position.value().x,10.0,ulps<=7); + assert_float_eq!(click_position.value().y,-200.0,ulps<=7); + + mouse.position.emit(Vector2::new(400.0,0.5)); + mouse.down.emit(Button::from_code(0)); + assert_float_eq!(click_position.value().x,410.0,ulps<=7); + assert_float_eq!(click_position.value().y,-199.5,ulps<=7); + } +} From 9973300d16d97c5e8fb98708465c152525008289 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Thu, 13 May 2021 12:05:11 +0200 Subject: [PATCH 22/25] doc: Update overflow shape docs. --- src/rust/ensogl/lib/components/src/selector/shape.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs index bc6e20ba78..703f3203ab 100644 --- a/src/rust/ensogl/lib/components/src/selector/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -122,8 +122,8 @@ pub mod track { // === Overflow === // ================ -/// Utility struct that contains the overflow shape, and some metadata that can be used to place and -/// align it. +/// Struct that contains the shape used to indicate an overflow (a triangle), and some metadata +/// that can be used to place and align it. struct OverflowShape { pub width : Var, pub height : Var, From cdd0b3bb2bf5f9a58e7a25f9e10dc80bf6be2f99 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Fri, 21 May 2021 10:03:20 +0200 Subject: [PATCH 23/25] style: Fix style. --- src/rust/ensogl/lib/components/src/component.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/component.rs b/src/rust/ensogl/lib/components/src/component.rs index a87eef8830..9b66264646 100644 --- a/src/rust/ensogl/lib/components/src/component.rs +++ b/src/rust/ensogl/lib/components/src/component.rs @@ -52,17 +52,17 @@ pub trait Frp : Default + CommandApi { /// Base struct for UI components in EnsoGL. Contains the Data/Shape model and the FPR exposing its /// behaviour. #[derive(Clone,CloneRef,Debug)] -pub struct Component { +pub struct Component { /// Public FRP api of the Component. pub frp : Rc, model : Rc, /// Reference to the application the Component belongs to. Generally required for implementing /// `application::View` and initialising the `Mode`l and `Frp` and thus provided by the /// `Component`. - pub app : Application, + pub app : Application, } -impl> Component { +impl> Component { /// Constructor. pub fn new(app:&Application) -> Self { let app = app.clone_ref(); @@ -75,17 +75,18 @@ impl> Component { } } -impl display::Object for Component { +impl display::Object for Component { fn display_object(&self) -> &display::object::Instance { &self.model.display_object() } } -impl> Deref for Component { +impl> Deref for Component { type Target = F; fn deref(&self) -> &Self::Target { &self.frp } } -impl application::command::FrpNetworkProvider for Component { +impl application::command::FrpNetworkProvider +for Component { fn network(&self) -> &frp::Network { self.frp.network() } } From 3087c09fdf6176b4cffb5f368fa5b50e38ab6e55 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Fri, 21 May 2021 10:03:40 +0200 Subject: [PATCH 24/25] build: Update lock file. --- src/rust/Cargo.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index c2250f620d..b791c647d2 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -881,7 +881,7 @@ dependencies = [ "ensogl-core", "ensogl-text", "ensogl-theme", - "float_eq 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "float_eq", "wasm-bindgen-test", ] @@ -1043,6 +1043,7 @@ dependencies = [ name = "float_eq" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb23b6902f3cdc0544f9916b4c092f46f4ff984e219d5a0c538b6b3539885af3" [[package]] name = "fnv" From 1f831d847ced4438c5a21492db657a37915badb5 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Fri, 21 May 2021 10:09:24 +0200 Subject: [PATCH 25/25] fix: Compiler upgrade issues. --- src/rust/ensogl/example/src/slider.rs | 1 - src/rust/ensogl/lib/components/src/component.rs | 2 +- src/rust/ensogl/lib/components/src/lib.rs | 1 - src/rust/ensogl/lib/components/src/selector/bounds.rs | 4 ++-- src/rust/ensogl/lib/components/src/selector/model.rs | 4 ++-- src/rust/ensogl/lib/components/src/selector/shape.rs | 4 ++-- .../lib/core/src/display/shape/primitive/style_watch.rs | 2 +- 7 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs index d660cb1f36..72eac2af88 100644 --- a/src/rust/ensogl/example/src/slider.rs +++ b/src/rust/ensogl/example/src/slider.rs @@ -23,7 +23,6 @@ use ensogl_theme as theme; #[allow(dead_code)] pub fn entry_point_slider() { web::forward_panic_hook_to_console(); - web::set_stdout(); web::set_stack_trace_limit(); run_once_initialized(|| { let app = Application::new(&web::get_html_element_by_id("root").unwrap()); diff --git a/src/rust/ensogl/lib/components/src/component.rs b/src/rust/ensogl/lib/components/src/component.rs index 9b66264646..f67509b855 100644 --- a/src/rust/ensogl/lib/components/src/component.rs +++ b/src/rust/ensogl/lib/components/src/component.rs @@ -71,7 +71,7 @@ impl> Component { let style = StyleWatchFrp::new(&app.display.scene().style_sheet); frp.init(&app,&model,&style); let frp = Rc::new(frp); - Self {model,frp,app} + Self{frp,model,app} } } diff --git a/src/rust/ensogl/lib/components/src/lib.rs b/src/rust/ensogl/lib/components/src/lib.rs index 486bf6d039..e608303655 100644 --- a/src/rust/ensogl/lib/components/src/lib.rs +++ b/src/rust/ensogl/lib/components/src/lib.rs @@ -4,7 +4,6 @@ #![feature(option_result_contains)] #![feature(trait_alias)] -#![feature(clamp)] #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] diff --git a/src/rust/ensogl/lib/components/src/selector/bounds.rs b/src/rust/ensogl/lib/components/src/selector/bounds.rs index 8eda4da2cf..03ad3bcd28 100644 --- a/src/rust/ensogl/lib/components/src/selector/bounds.rs +++ b/src/rust/ensogl/lib/components/src/selector/bounds.rs @@ -43,7 +43,7 @@ impl Bounds { /// Return the distance between start and end point. pub fn width(self) -> f32 { - (self.end - self.start) + self.end - self.start } } @@ -73,7 +73,7 @@ pub fn normalise_value((value,bounds):&(f32,Bounds)) -> f32 { /// value <- all(&bounds,&normalised).map(absolute_value); /// ```` pub fn absolute_value((bounds,normalised_value):&(Bounds,f32)) -> f32 { - ((normalised_value * bounds.width()) + bounds.start) + (normalised_value * bounds.width()) + bounds.start } /// Returns the normalised value that correspond to the click position on the shape. diff --git a/src/rust/ensogl/lib/components/src/selector/model.rs b/src/rust/ensogl/lib/components/src/selector/model.rs index 42496e3433..55aa35da15 100644 --- a/src/rust/ensogl/lib/components/src/selector/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/model.rs @@ -107,8 +107,8 @@ impl component::Model for Model { caption_center.remove_from_scene_layer_DEPRECATED(&scene.layers.main); caption_center.add_to_scene_layer_DEPRECATED(&scene.layers.label); - Self{root,label,background,track,left_overflow,right_overflow,caption_left,caption_center, - label_left,label_right,track_handle_left,track_handle_right} + Self{background,track,track_handle_left,track_handle_right,left_overflow,right_overflow, + label,label_left,label_right,caption_left,caption_center,root} } } diff --git a/src/rust/ensogl/lib/components/src/selector/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs index 703f3203ab..d2a5c4c678 100644 --- a/src/rust/ensogl/lib/components/src/selector/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -145,7 +145,7 @@ impl OverflowShape { let hover_area = hover_area.fill(HOVER_COLOR); let shape = (shape + hover_area).into(); - OverflowShape {shape,width,height} + OverflowShape{width,height,shape} } } @@ -157,7 +157,7 @@ pub mod left_overflow { ensogl_core::define_shape_system! { (style:Style) { let overflow_shape = OverflowShape::new(style); - let shape = overflow_shape.shape.rotate(-90.0_f32.to_radians().radians()); + let shape = overflow_shape.shape.rotate((-90.0_f32).to_radians().radians()); shape.into() } } diff --git a/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs b/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs index 02123030e5..1135c97645 100644 --- a/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs +++ b/src/rust/ensogl/lib/core/src/display/shape/primitive/style_watch.rs @@ -87,7 +87,7 @@ impl StyleWatchFrp { let network = &self.network; let (source,current) = self.get_internal(path); frp::extend! { network - value <- source.map(|t| t.number().unwrap_or_else(|| 0.0)); + value <- source.map(|t| t.number().unwrap_or(0.0)); sampler <- value.sampler(); } source.emit(current);