From c6c5a9414c8ecf06bbc85a1fcb731e9874600a00 Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Wed, 9 Jun 2021 14:54:11 +0200 Subject: [PATCH 01/11] feat: Implement scrollbar component. --- src/rust/ensogl/example/src/slider.rs | 21 +++ src/rust/ensogl/lib/components/src/lib.rs | 3 +- .../ensogl/lib/components/src/scrollbar.rs | 152 ++++++++++++++++++ .../ensogl/lib/components/src/selector.rs | 7 +- .../lib/components/src/selector/bounds.rs | 5 + .../ensogl/lib/components/src/selector/frp.rs | 19 ++- .../lib/components/src/selector/number.rs | 6 +- .../lib/components/src/selector/shape.rs | 16 +- src/rust/lib/types/src/algebra.rs | 3 + 9 files changed, 216 insertions(+), 16 deletions(-) create mode 100644 src/rust/ensogl/lib/components/src/scrollbar.rs diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs index 72eac2af88..18b478d9e5 100644 --- a/src/rust/ensogl/example/src/slider.rs +++ b/src/rust/ensogl/example/src/slider.rs @@ -9,6 +9,7 @@ use ensogl_core::display::object::ObjectOps; use ensogl_core::system::web; use ensogl_gui_components::selector::Bounds; use ensogl_gui_components::selector; +use ensogl_gui_components::scrollbar::Scrollbar; use ensogl_text_msdf_sys::run_once_initialized; use ensogl_theme as theme; @@ -45,6 +46,13 @@ fn make_range_picker(app:&Application) -> Leak { Leak::new(slider) } +fn make_scrollbar(app:&Application) -> Leak { + let scrollbar = app.new_view::(); + scrollbar.frp.resize(Vector2(200.0,50.0)); + app.display.add_child(&scrollbar); + Leak::new(scrollbar) +} + // ======================== @@ -75,4 +83,17 @@ fn init(app:&Application) { 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)); + + let scrollbar = make_scrollbar(app); + scrollbar.inner().set_rotation_z(90.0_f32.to_radians()); + scrollbar.inner().set_position_x(-300.0); + scrollbar.inner().set_position_y(75.0); + scrollbar.inner().set_track_color(color::Rgba::new(0.5,0.70,0.70,1.0)); + + let scrollbar2 = make_scrollbar(app); + scrollbar2.inner().set_position_x(-300.0); + scrollbar2.inner().set_position_y(-75.0); + scrollbar2.inner().set_track_color(color::Rgba::new(0.5,0.70,0.70,1.0)); + scrollbar2.inner().set_overall_bounds(Bounds::new(0.0,100.0)); + scrollbar2.inner().set_track(Bounds::new(0.0,10.0)); } diff --git a/src/rust/ensogl/lib/components/src/lib.rs b/src/rust/ensogl/lib/components/src/lib.rs index e608303655..d8fdddd9ea 100644 --- a/src/rust/ensogl/lib/components/src/lib.rs +++ b/src/rust/ensogl/lib/components/src/lib.rs @@ -20,8 +20,9 @@ pub mod component; pub mod drop_down_menu; pub mod label; pub mod list_view; -pub mod shadow; +pub mod scrollbar; pub mod selector; +pub mod shadow; pub mod toggle_button; /// Commonly used types and functions. diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs new file mode 100644 index 0000000000..3eff08a452 --- /dev/null +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -0,0 +1,152 @@ +//! Module that contains a scrollbar component that can be used to implement scrollable components. + +use crate::prelude::*; + +use enso_frp as frp; +use ensogl_core::Animation; +use ensogl_core::application::Application; +use ensogl_core::application; +use ensogl_core::data::color; +use ensogl_core::display::shape::*; +use ensogl_core::display::shape::StyleWatchFrp; +use ensogl_theme as theme; + +use crate::component; +use crate::selector::Bounds; +use crate::selector::bounds::absolute_value; +use crate::selector::bounds::normalise_value; +use crate::selector::bounds::should_clamp_with_overflow; +use crate::selector::model::Model; +use crate::selector; + + + +// =========== +// === Frp === +// =========== + +ensogl_core::define_endpoints! { + Input { + resize(Vector2), + + set_track(Bounds), + set_overall_bounds(Bounds), + + set_left_corner_round(bool), + set_right_corner_round(bool), + set_track_color(color::Rgba), + } + Output { + track(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 track_position_lower = Animation::new(&network); + let track_position_upper = Animation::new(&network); + + let base_frp = selector::Frp::new(model, style, network, frp.resize.clone().into(), mouse); + + model.use_track_handles(false); + model.show_left_overflow(false); + model.show_right_overflow(false);let style_track_color = style.get_color(theme::component::slider::track::color); + + frp::extend! { network + // Simple Inputs + 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)); + + // API - `set_track` + track_position_lower.target <+ frp.set_track.map(|b| b.start); + track_position_upper.target <+ frp.set_track.map(|b| b.end); + track_position_lower.skip <+ frp.set_track.constant(()); + track_position_upper.skip <+ frp.set_track.constant(()); + + // Normalise values for internal use. + normalised_track_bounds <- all2(&frp.track,&frp.set_overall_bounds).map(|(track,overall)|{ + Bounds::new(normalise_value(&(track.start,*overall)),normalise_value(&(track.end,*overall))) + }); + normalised_track_center <- normalised_track_bounds.map(|bounds| bounds.center()); + + // Slider Updates + update_slider <- all(&normalised_track_bounds,&frp.resize); + eval update_slider(((value,size)) model.set_background_range(*value,*size)); + + // Mouse IO - Clicking + click_delta <- base_frp.background_click.map2(&normalised_track_center, + f!([model](click_value,current_value) { + let rotation = Rotation2::new(-model.rotation().z); + let rotated = rotation * click_value; + let direction = if rotated.x > *current_value { 1.0 } else { -1.0 }; + const SCALE: f32 = 0.05; + direction * SCALE + })); + click_animation <- click_delta.constant(true); + + // Mouse IO - Dragging + 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_delta.gate(&base_frp.is_dragging_track); + drag_animation <- drag_delta.constant(false); + + // Mouse IO - Event Evaluation + should_animate <- any(&click_animation,&drag_animation); + + drag_center_delta <- any(&drag_delta,&click_delta); + drag_update <- drag_center_delta.map2(&normalised_track_bounds,|delta,Bounds{start,end}| + Bounds::new(start+delta,end+delta) + ); + + is_in_bounds <- drag_update.map(|value| should_clamp_with_overflow(value,&None)); + + new_value_absolute <- all(&frp.set_overall_bounds,&drag_update).map(|(bounds,Bounds{start,end})| + Bounds::new( + absolute_value(&(*bounds,*start)),absolute_value(&(*bounds,*end))).sorted() + ).gate(&is_in_bounds); + + new_value_lower <- new_value_absolute.map(|b| b.start); + new_value_upper <- new_value_absolute.map(|b| b.end); + + track_position_lower.target <+ new_value_lower.gate(&is_in_bounds); + track_position_upper.target <+ new_value_upper.gate(&is_in_bounds); + + track_position_lower.skip <+ should_animate.on_false(); + track_position_upper.skip <+ should_animate.on_false(); + + track_position <- all(&track_position_lower.value,&track_position_upper.value).map( + |(lower,upper)| Bounds::new(*lower,*upper)); + + frp.source.track <+ track_position; + } + + // Init defaults + frp.set_overall_bounds(Bounds::new(0.0,1.0)); + frp.set_track(Bounds::new(0.25,0.55)); + frp.set_left_corner_round(true); + frp.set_right_corner_round(true); + frp.set_track_color(style_track_color.value()); + } +} + + + +// ================= +// === Scrollbar === +// ================= + +/// Scrollbar component that can be used to implement scrollable components. +pub type Scrollbar = crate::component::Component; + +impl application::View for Scrollbar { + fn label() -> &'static str { "Scrollbar" } + fn new(app:&Application) -> Self { Scrollbar::new(app) } + fn app(&self) -> &Application { &self.app } +} diff --git a/src/rust/ensogl/lib/components/src/selector.rs b/src/rust/ensogl/lib/components/src/selector.rs index 05e9cdccf2..c64f7c1f9a 100644 --- a/src/rust/ensogl/lib/components/src/selector.rs +++ b/src/rust/ensogl/lib/components/src/selector.rs @@ -9,10 +9,11 @@ //! The only things exposed form this module are the `NumberPicker`, `NumberRangePicker`, their //! FRPs and the `Bounds` struct. -mod bounds; +pub(crate) mod bounds; +pub(crate) mod model; + mod decimal_aligned; mod frp; -mod model; mod number; mod range; mod shape; @@ -21,8 +22,8 @@ use ensogl_core::application; use ensogl_core::application::Application; pub use bounds::Bounds; +pub(crate) use frp::*; use model::*; -use frp::*; diff --git a/src/rust/ensogl/lib/components/src/selector/bounds.rs b/src/rust/ensogl/lib/components/src/selector/bounds.rs index 03ad3bcd28..0135a07217 100644 --- a/src/rust/ensogl/lib/components/src/selector/bounds.rs +++ b/src/rust/ensogl/lib/components/src/selector/bounds.rs @@ -45,6 +45,11 @@ impl Bounds { pub fn width(self) -> f32 { self.end - self.start } + + /// Return the center between start and end. + pub fn center(self) -> f32 { + self.start + self.width() / 2.0 + } } impl From<(f32,f32)> for Bounds { diff --git a/src/rust/ensogl/lib/components/src/selector/frp.rs b/src/rust/ensogl/lib/components/src/selector/frp.rs index ce779d4776..7f4d319b35 100644 --- a/src/rust/ensogl/lib/components/src/selector/frp.rs +++ b/src/rust/ensogl/lib/components/src/selector/frp.rs @@ -13,6 +13,7 @@ use crate::shadow; use super::model::Model; use super::shape::shape_is_dragged; +use super::shape::relative_shape_down_position; @@ -55,6 +56,12 @@ pub struct Frp { 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, + /// Position of a click on the background. Position is given relative to the overall shape + /// origin and normalised to the shape size. + pub background_click : frp::Stream, + /// Position of a click on the track shape. Position is given relative to the overall shape + /// origin and normalised to the shape size. + pub track_click : frp::Stream, } impl Frp { @@ -77,6 +84,16 @@ impl Frp { let is_dragging_right_handle = shape_is_dragged( network,&model.track_handle_right.events,mouse); + let model_fn = model.clone_ref(); + let base_position = move || model_fn.position().xy(); + let background_click = relative_shape_down_position( + base_position,network,&model.background.events,mouse); + + let model_fn = model.clone_ref(); + let base_position = move || model_fn.position().xy(); + let track_click = relative_shape_down_position( + base_position,network,&model.track.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); @@ -121,6 +138,6 @@ impl Frp { 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} + is_dragging_right_handle,is_dragging_any,background_click,track_click} } } diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index 352014bd06..93f759f519 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -15,7 +15,7 @@ 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::shape::relative_shape_down_position; use super::model::Model; @@ -56,12 +56,12 @@ impl component::Frp for Frp { let madel_fn = model.clone_ref(); let base_position = move || madel_fn.position().xy(); - let background_click = relative_shape_click_position( + let background_click = relative_shape_down_position( 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( + let track_click = relative_shape_down_position( base_position,network,&model.track.events,mouse); let style_track_color = style.get_color(theme::component::slider::track::color); diff --git a/src/rust/ensogl/lib/components/src/selector/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs index d2a5c4c678..c7bf5615ce 100644 --- a/src/rust/ensogl/lib/components/src/selector/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -208,21 +208,21 @@ 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( +pub fn relative_shape_down_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| + mouse_down <- mouse.down.constant(()); + over_shape <- bool(&shape.mouse_out,&shape.mouse_over); + mouse_down_over_shape <- mouse_down.gate(&over_shape); + click_positon <- mouse.position.sample(&mouse_down_over_shape); + click_positon <- click_positon.map(move |pos| pos - base_position() ); } - background_click_positon + click_positon } @@ -275,7 +275,7 @@ mod tests { 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 click_position = relative_shape_down_position(base_position, &network,&shape,&mouse); let _watch = click_position.register_watch(); shape.mouse_over.emit(()); diff --git a/src/rust/lib/types/src/algebra.rs b/src/rust/lib/types/src/algebra.rs index 9e4372e4b9..52928fc46a 100644 --- a/src/rust/lib/types/src/algebra.rs +++ b/src/rust/lib/types/src/algebra.rs @@ -42,6 +42,9 @@ mod vectors { pub type Vector3 = nalgebra::Vector3; pub type Vector4 = nalgebra::Vector4; + pub type Rotation2 = nalgebra::Rotation2; + pub type Rotation3 = nalgebra::Rotation3; + pub fn Vector2(t1:T,t2:T) -> Vector2 { Vector2::new(t1,t2) } pub fn Vector3(t1:T,t2:T,t3:T) -> Vector3 { Vector3::new(t1,t2,t3) } pub fn Vector4(t1:T,t2:T,t3:T,t4:T) -> Vector4 { Vector4::new(t1,t2,t3,t4) } From 46aba916bffc1daae9f6472e82cc68e95ab8875f Mon Sep 17 00:00:00 2001 From: Michael Mauderer Date: Tue, 15 Jun 2021 14:25:55 +0200 Subject: [PATCH 02/11] feat: Implement review feedback: hide background, disallow orthogonal scrolling, optimise click jump length. --- .../ensogl/lib/components/src/scrollbar.rs | 49 ++++++++++++------- .../ensogl/lib/components/src/selector/frp.rs | 10 +++- .../lib/components/src/selector/model.rs | 44 +++++++++++++++-- .../lib/components/src/selector/number.rs | 2 + .../lib/components/src/selector/range.rs | 1 + .../lib/components/src/selector/shape.rs | 24 +++++---- 6 files changed, 99 insertions(+), 31 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 3eff08a452..9e70f1cbe8 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -18,6 +18,15 @@ use crate::selector::bounds::normalise_value; use crate::selector::bounds::should_clamp_with_overflow; use crate::selector::model::Model; use crate::selector; +use ensogl_core::display::object::ObjectOps; + + +// ================= +// === Constants === +// ================= + +/// Amount the scrollbar moves ona single click, relative to the viewport width. +const CLICK_JUMP_PERCENTAGE: f32 = 0.80; @@ -32,8 +41,6 @@ ensogl_core::define_endpoints! { set_track(Bounds), set_overall_bounds(Bounds), - set_left_corner_round(bool), - set_right_corner_round(bool), set_track_color(color::Rgba), } Output { @@ -41,6 +48,14 @@ ensogl_core::define_endpoints! { } } +/// Returns the result of the projection of the given `vector` to the x-axis of the coordinate +/// system of the given `shape`. For example, if the vector is parallel to the x-axis of the shape +/// coordinate system, it is returned unchanged, if it is perpendicular the zero vector is returned. +fn vector_aligned_with_object(vector:&Vector2,object:impl ObjectOps) -> Vector2 { + let object_rotation = Rotation2::new(-object.rotation().z); + object_rotation * vector +} + impl component::Frp for Frp { fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp){ let frp = &self; @@ -53,13 +68,15 @@ impl component::Frp for Frp { let base_frp = selector::Frp::new(model, style, network, frp.resize.clone().into(), mouse); model.use_track_handles(false); + model.set_track_corner_round(true); + model.show_background(false); model.show_left_overflow(false); - model.show_right_overflow(false);let style_track_color = style.get_color(theme::component::slider::track::color); + model.show_right_overflow(false); + + let style_track_color = style.get_color(theme::component::slider::track::color); frp::extend! { network // Simple Inputs - 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)); // API - `set_track` @@ -73,27 +90,27 @@ impl component::Frp for Frp { Bounds::new(normalise_value(&(track.start,*overall)),normalise_value(&(track.end,*overall))) }); normalised_track_center <- normalised_track_bounds.map(|bounds| bounds.center()); + normalised_track_width <- normalised_track_bounds.map(|bounds| bounds.width()); // Slider Updates update_slider <- all(&normalised_track_bounds,&frp.resize); eval update_slider(((value,size)) model.set_background_range(*value,*size)); // Mouse IO - Clicking - click_delta <- base_frp.background_click.map2(&normalised_track_center, - f!([model](click_value,current_value) { - let rotation = Rotation2::new(-model.rotation().z); - let rotated = rotation * click_value; - let direction = if rotated.x > *current_value { 1.0 } else { -1.0 }; - const SCALE: f32 = 0.05; - direction * SCALE + click_delta <- base_frp.background_click.map3(&normalised_track_center,&normalised_track_width, + f!([model](click_value,current_value,track_width) { + let shape_aligned = vector_aligned_with_object(click_value,&model); + let direction = if shape_aligned.x > *current_value { 1.0 } else { -1.0 }; + direction * track_width * CLICK_JUMP_PERCENTAGE })); click_animation <- click_delta.constant(true); // Mouse IO - Dragging 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, f!([model](delta,width) { + let shape_aligned = vector_aligned_with_object(delta,&model); + (shape_aligned.x) / width + })); drag_delta <- drag_delta.gate(&base_frp.is_dragging_track); drag_animation <- drag_delta.constant(false); @@ -130,8 +147,6 @@ impl component::Frp for Frp { // Init defaults frp.set_overall_bounds(Bounds::new(0.0,1.0)); frp.set_track(Bounds::new(0.25,0.55)); - 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/frp.rs b/src/rust/ensogl/lib/components/src/selector/frp.rs index 7f4d319b35..0a3041a4cf 100644 --- a/src/rust/ensogl/lib/components/src/selector/frp.rs +++ b/src/rust/ensogl/lib/components/src/selector/frp.rs @@ -62,6 +62,8 @@ pub struct Frp { /// Position of a click on the track shape. Position is given relative to the overall shape /// origin and normalised to the shape size. pub track_click : frp::Stream, + /// Indicates whether the track is hovered.. + pub track_hover : frp::Stream, } impl Frp { @@ -101,6 +103,9 @@ impl Frp { model.caption_left.set_position_y(text_size.value()/2.0); model.caption_center.set_position_y(text_size.value()/2.0); + let bg_color = style.get_color(theme::component::slider::background); + model.set_background_color(bg_color.value()); + frp::extend! { network // Style updates. @@ -110,6 +115,7 @@ impl Frp { model.label_right.set_position_y(size / 2.0); model.label_left.set_position_y(size / 2.0); }); + eval bg_color ((color) model.set_background_color(*color) ); // Caption updates. update_caption_position <- all(&size,&text_size); @@ -134,10 +140,12 @@ impl Frp { &is_dragging_overflow, &is_dragging_handle, ); + + track_hover <- bool(&model.track.events.mouse_out,&model.track.events.mouse_over); } 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,background_click,track_click} + is_dragging_right_handle,is_dragging_any,background_click,track_click,track_hover} } } diff --git a/src/rust/ensogl/lib/components/src/selector/model.rs b/src/rust/ensogl/lib/components/src/selector/model.rs index 89bc23e4af..f28ec96841 100644 --- a/src/rust/ensogl/lib/components/src/selector/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/model.rs @@ -64,6 +64,11 @@ pub struct Model { /// Shape root that all other elements are parented to. Should be used to place the shapes as /// a group. pub root : display::object::Instance, + + background_color : Rc>, + track_color : Rc>, + background_left_corner_roundness : Rc>, + background_right_corner_roundness : Rc>, } impl component::Model for Model { @@ -81,6 +86,10 @@ impl component::Model for Model { 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 background_color = default(); + let track_color = default(); + let background_left_corner_roundness = default(); + let background_right_corner_roundness = default(); let app = app.clone_ref(); let scene = app.display.scene(); @@ -108,7 +117,8 @@ impl component::Model for Model { caption_center.add_to_scene_layer(&scene.layers.label); Self{background,track,track_handle_left,track_handle_right,left_overflow,right_overflow, - label,label_left,label_right,caption_left,caption_center,root} + label,label_left,label_right,caption_left,caption_center,root,background_color, + track_color,background_left_corner_roundness,background_right_corner_roundness} } } @@ -169,6 +179,11 @@ impl Model { self.track.right.set(value); } + pub fn set_background_color(&self, color:color::Rgba) { + self.background_color.as_ref().replace(color); + self.background.color.set(color.into()); + } + /// 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 @@ -225,18 +240,41 @@ impl Model { 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) + self.track.corner_left.set(corner_roundness); + self.background_left_corner_roundness.set(value); } 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) + self.track.corner_right.set(corner_roundness); + self.background_right_corner_roundness.set(value); } pub fn set_track_color(&self, color:color::Rgba) { self.track.track_color.set(color.into()); } + + pub fn set_track_corner_round(&self, value:bool) { + let corner_roundness = if value { 1.0 } else { 0.0 }; + self.track.corner_inner.set(corner_roundness) + } + + pub fn show_background(&self, value:bool) { + if value { + self.background.show_shadow.set(1.0); + self.background.color.set(self.background_color.as_ref().clone().into_inner().into()); + let left_corner_roundness = if self.background_left_corner_roundness.get() { 1.0 } else { 0.0 }; + let right_corner_roundness = if self.background_right_corner_roundness.get() { 1.0 } else { 0.0 }; + self.track.corner_right.set(right_corner_roundness); + self.track.corner_left.set(left_corner_roundness); + } else { + self.background.color.set(HOVER_COLOR.into()); + self.background.show_shadow.set(0.0); + self.track.corner_right.set(0.0); + self.track.corner_left.set(0.0); + } + } } impl display::Object for Model { diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index 93f759f519..c4a8ecbf70 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -49,6 +49,8 @@ impl component::Frp for Frp { let scene = app.display.scene(); let mouse = &scene.mouse.frp; + model.show_background(true); + let base_frp = super::Frp::new(model,style,network,frp.resize.clone().into(),mouse); let track_shape_system = scene.shapes.shape_system(PhantomData::); diff --git a/src/rust/ensogl/lib/components/src/selector/range.rs b/src/rust/ensogl/lib/components/src/selector/range.rs index f2ab0f4d1c..9038cea183 100644 --- a/src/rust/ensogl/lib/components/src/selector/range.rs +++ b/src/rust/ensogl/lib/components/src/selector/range.rs @@ -49,6 +49,7 @@ impl component::Frp for Frp { let base_frp = super::Frp::new(model, style, network, frp.resize.clone().into(), mouse); model.use_track_handles(true); + model.show_background(true); let style_track_color = style.get_color(theme::component::slider::track::color); diff --git a/src/rust/ensogl/lib/components/src/selector/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs index c7bf5615ce..f72014cb58 100644 --- a/src/rust/ensogl/lib/components/src/selector/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -24,7 +24,8 @@ 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(); @@ -50,10 +51,10 @@ pub mod background { use super::*; ensogl_core::define_shape_system! { - (style:Style,corner_left:f32,corner_right:f32) { + (style:Style,corner_left:f32,corner_right:f32,color:Vector4,show_shadow: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 shadow = shadow::from_shape_with_alpha(background.shape.clone(), + &show_shadow,style); let background = background.shape.fill(color); (shadow + background).into() } @@ -97,14 +98,17 @@ pub mod track { use super::*; ensogl_core::define_shape_system! { - (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; + (style:Style,left:f32,right:f32,corner_left:f32,corner_right:f32,corner_inner:f32, + track_color:Vector4) { + let background = Background::new(&corner_left,&corner_right,style); + let width = background.width; + let height = background.height; + let corner_radius = corner_inner * &height/2.0; + let track_width = (&right - &left) * &width; let track_start = left * &width; - let track = Rect((&track_width,&height)); + let track = Rect((&track_width,&height)).corners_radius(corner_radius); 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); @@ -136,7 +140,7 @@ impl OverflowShape { 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 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); From b4abaa0a1118bf9c31883341de09e3cfa1d87ed2 Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Wed, 23 Jun 2021 09:29:37 +0100 Subject: [PATCH 03/11] Improve scrollbars and add scroll areas --- src/rust/ensogl/example/src/lib.rs | 1 + src/rust/ensogl/example/src/scroll_area.rs | 86 ++++++ src/rust/ensogl/example/src/slider.rs | 22 -- src/rust/ensogl/lib/components/src/lib.rs | 1 + .../ensogl/lib/components/src/scroll_area.rs | 161 ++++++++++ .../ensogl/lib/components/src/scrollbar.rs | 278 ++++++++++++------ .../ensogl/lib/components/src/selector/frp.rs | 18 +- .../lib/components/src/selector/model.rs | 22 +- .../lib/components/src/selector/number.rs | 9 +- .../lib/components/src/selector/shape.rs | 26 +- src/rust/ensogl/lib/theme/src/lib.rs | 3 +- 11 files changed, 479 insertions(+), 148 deletions(-) create mode 100644 src/rust/ensogl/example/src/scroll_area.rs create mode 100644 src/rust/ensogl/lib/components/src/scroll_area.rs diff --git a/src/rust/ensogl/example/src/lib.rs b/src/rust/ensogl/example/src/lib.rs index 3127337df9..b31e365e02 100644 --- a/src/rust/ensogl/example/src/lib.rs +++ b/src/rust/ensogl/example/src/lib.rs @@ -34,6 +34,7 @@ pub mod easing_animator; pub mod glyph_system; pub mod list_view; pub mod mouse_events; +pub mod scroll_area; pub mod shape_system; pub mod slider; pub mod sprite_system; diff --git a/src/rust/ensogl/example/src/scroll_area.rs b/src/rust/ensogl/example/src/scroll_area.rs new file mode 100644 index 0000000000..437296c594 --- /dev/null +++ b/src/rust/ensogl/example/src/scroll_area.rs @@ -0,0 +1,86 @@ +//! A debug scene which shows the scroll area. + +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_text_msdf_sys::run_once_initialized; +use ensogl_theme as theme; +use ensogl_gui_components::scroll_area::ScrollArea; +use ensogl_core::display::shape::{Circle, Rect, ShapeSystem}; +use ensogl_core::display::shape::PixelDistance; +use ensogl_core::display::shape::ShapeOps; +use ensogl_core::display::Sprite; + + + +// =================== +// === Entry Point === +// =================== + +/// An entry point. +#[wasm_bindgen] +pub fn entry_point_scroll_area() { + web::forward_panic_hook_to_console(); + 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); + }); +} + + + +// ======================== +// === Init Application === +// ======================== + +fn init(app:&Application) { + theme::builtin::dark::register(&app); + theme::builtin::light::register(&app); + theme::builtin::light::enable(&app); + + let scene = app.display.scene(); + scene.camera().set_position_xy(Vector2(100.0,-100.0)); + + + // === Background === + + let background_color = color::Rgba::new(0.9,0.9,0.9,1.0); + let background_size = (200.px(), 200.px()); + let background_shape = Rect(background_size).corners_radius(5.5.px()).fill(background_color); + let background_system = ShapeSystem::new(scene,background_shape); + let background: Sprite = background_system.new_instance(); + scene.add_child(&background); + background.size.set(Vector2::new(200.0,200.0)); + background.set_position_x(100.0); + background.set_position_y(-100.0); + std::mem::forget(background); + + + // === Scroll Area === + + let scroll_area = ScrollArea::new(&app); + app.display.add_child(&scroll_area); + scroll_area.resize(Vector2(200.0,200.0)); + scroll_area.set_content_width(300.0); + scroll_area.set_content_height(1000.0); + + + // === Content === + + let sprite_system = ShapeSystem::new(scene,&Circle(50.px())); + let sprite: Sprite = sprite_system.new_instance(); + scroll_area.content.add_child(&sprite); + sprite.size.set(Vector2::new(100.0,100.0)); + sprite.set_position_x(100.0); + sprite.set_position_y(-100.0); + std::mem::forget(sprite); + + + std::mem::forget(scroll_area); +} diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs index 18b478d9e5..822f305ed4 100644 --- a/src/rust/ensogl/example/src/slider.rs +++ b/src/rust/ensogl/example/src/slider.rs @@ -9,12 +9,10 @@ use ensogl_core::display::object::ObjectOps; use ensogl_core::system::web; use ensogl_gui_components::selector::Bounds; use ensogl_gui_components::selector; -use ensogl_gui_components::scrollbar::Scrollbar; use ensogl_text_msdf_sys::run_once_initialized; use ensogl_theme as theme; - // =================== // === Entry Point === // =================== @@ -46,13 +44,6 @@ fn make_range_picker(app:&Application) -> Leak { Leak::new(slider) } -fn make_scrollbar(app:&Application) -> Leak { - let scrollbar = app.new_view::(); - scrollbar.frp.resize(Vector2(200.0,50.0)); - app.display.add_child(&scrollbar); - Leak::new(scrollbar) -} - // ======================== @@ -83,17 +74,4 @@ fn init(app:&Application) { 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)); - - let scrollbar = make_scrollbar(app); - scrollbar.inner().set_rotation_z(90.0_f32.to_radians()); - scrollbar.inner().set_position_x(-300.0); - scrollbar.inner().set_position_y(75.0); - scrollbar.inner().set_track_color(color::Rgba::new(0.5,0.70,0.70,1.0)); - - let scrollbar2 = make_scrollbar(app); - scrollbar2.inner().set_position_x(-300.0); - scrollbar2.inner().set_position_y(-75.0); - scrollbar2.inner().set_track_color(color::Rgba::new(0.5,0.70,0.70,1.0)); - scrollbar2.inner().set_overall_bounds(Bounds::new(0.0,100.0)); - scrollbar2.inner().set_track(Bounds::new(0.0,10.0)); } diff --git a/src/rust/ensogl/lib/components/src/lib.rs b/src/rust/ensogl/lib/components/src/lib.rs index d8fdddd9ea..20bd8b85a3 100644 --- a/src/rust/ensogl/lib/components/src/lib.rs +++ b/src/rust/ensogl/lib/components/src/lib.rs @@ -20,6 +20,7 @@ pub mod component; pub mod drop_down_menu; pub mod label; pub mod list_view; +pub mod scroll_area; pub mod scrollbar; pub mod selector; pub mod shadow; diff --git a/src/rust/ensogl/lib/components/src/scroll_area.rs b/src/rust/ensogl/lib/components/src/scroll_area.rs new file mode 100644 index 0000000000..57b1926caf --- /dev/null +++ b/src/rust/ensogl/lib/components/src/scroll_area.rs @@ -0,0 +1,161 @@ +//! This module provides the [`ScrollArea`] component. It displays two scrollbars, for horizontal +//! and vertical scrolling. Content can be added to the `content` attribute. The content size has +//! to be set through `set_content_height` and `set_content_width`. The component is anchored at +//! the top left corner. All scroll coordinates describe the point of the `content` object at that +//! corner. + +use crate::prelude::*; + +use crate::scrollbar; +use crate::scrollbar::Scrollbar; + +use enso_frp as frp; +use ensogl_core::display; +use ensogl_core::display::object::ObjectOps; +use ensogl_core::application::Application; +use ensogl_core::control::io::mouse; +use ensogl_core::control::callback; + + + +// =========== +// === Frp === +// =========== + +ensogl_core::define_endpoints! { + Input { + /// Set the width and height in px. + resize (Vector2), + /// Set the content width in px. Affects how far one can scroll horizontally. + set_content_width (f32), + /// Set the content height in px. Affects how far one can scroll vertically. + set_content_height (f32), + /// Scrolls smoothly to the given x coordinate. + scroll_to_x (f32), + /// Scrolls smoothly to the given y coordinate. + scroll_to_y (f32), + /// Jumps instantly to the given x coordinate, without animation. + jump_to_x (f32), + /// Jumps instantly to the given y coordinate, without animation. + jump_to_y (f32), + } + Output { + /// The content's x coordinate at the left edge of the area. + scroll_position_x (f32), + /// The content's y coordinate at the top edge of the area. + scroll_position_y (f32), + } +} + + + +// =================== +// === Scroll Area === +// =================== + +/// See module description +#[derive(Debug,Clone,CloneRef)] +pub struct ScrollArea { + /// All objects that should be inside the scroll area and affected by the scrolling, have to be + /// added as children to `content`. + pub content : display::object::Instance, + display_object : display::object::Instance, + h_scrollbar : Scrollbar, + v_scrollbar : Scrollbar, + scroll_handler_handle : callback::Handle, + frp : Frp, +} + +impl Deref for ScrollArea { + type Target = Frp; + + fn deref(&self) -> &Self::Target { + &self.frp + } +} + +impl display::Object for ScrollArea { + fn display_object(&self) -> &display::object::Instance { + &self.display_object + } +} + +impl ScrollArea { + /// Create a new scroll area for use in the given application. + pub fn new(app:&Application) -> ScrollArea { + let scene = app.display.scene(); + let logger = Logger::new("ScrollArea"); + let display_object = display::object::Instance::new(&logger); + + let content = display::object::Instance::new(&logger); + display_object.add_child(&content); + + let h_scrollbar = Scrollbar::new(&app); + display_object.add_child(&h_scrollbar); + + let v_scrollbar = Scrollbar::new(&app); + display_object.add_child(&v_scrollbar); + v_scrollbar.set_rotation_z(-90.0_f32.to_radians()); + + let frp = Frp::new(); + let network = &frp.network; + + frp::extend! { network + + // === Size and Position === + + h_scrollbar.set_max <+ frp.set_content_width; + v_scrollbar.set_max <+ frp.set_content_height; + h_scrollbar.set_thumb_size <+ frp.resize.map(|size| size.x); + v_scrollbar.set_thumb_size <+ frp.resize.map(|size| size.y); + h_scrollbar.set_length <+ frp.resize.map(|size| size.x); + v_scrollbar.set_length <+ frp.resize.map(|size| size.y); + + eval frp.resize([h_scrollbar,v_scrollbar](size) { + h_scrollbar.set_position_y(-size.y+scrollbar::WIDTH/2.0); + v_scrollbar.set_position_x(size.x-scrollbar::WIDTH/2.0); + h_scrollbar.set_position_x(size.x/2.0); + v_scrollbar.set_position_y(-size.y/2.0); + }); + + + // === Scrolling === + + h_scrollbar.scroll_to <+ frp.scroll_to_x; + v_scrollbar.scroll_to <+ frp.scroll_to_y; + h_scrollbar.jump_to <+ frp.jump_to_x; + v_scrollbar.jump_to <+ frp.jump_to_y; + + frp.source.scroll_position_x <+ h_scrollbar.thumb_position.map(|x| -x); + frp.source.scroll_position_y <+ v_scrollbar.thumb_position; + + eval frp.scroll_position_x((&pos) content.set_position_x(pos)); + eval frp.scroll_position_y((&pos) content.set_position_y(pos)); + } + + + // === Mouse Wheel === + + let mouse = &scene.mouse; + frp::extend! { network + hovering <- all_with(&mouse.frp.position,&frp.resize, + f!([scene,display_object](&pos,&size) { + let local_pos = scene.screen_to_object_space(&display_object,pos); + (0.0..size.x).contains(&local_pos.x) && (-size.y..0.0).contains(&local_pos.y) + })); + hovering <- hovering.sampler(); + } + + let mouse_manager = &mouse.mouse_manager; + let scroll_handler = f!([v_scrollbar,h_scrollbar](event:&mouse::OnWheel) + if hovering.value() { + h_scrollbar.scroll_by(event.delta_x() as f32); + v_scrollbar.scroll_by(event.delta_y() as f32); + } + ); + let scroll_handler_handle = mouse_manager.on_wheel.add(scroll_handler); + + + ScrollArea {content,display_object,h_scrollbar,v_scrollbar,scroll_handler_handle,frp} + } +} diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 9e70f1cbe8..273e2e6521 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -1,4 +1,14 @@ //! Module that contains a scrollbar component that can be used to implement scrollable components. +//! We say "thumb" to mean the object inside the bar that indicates the scroll position and can be +//! dragged to change that position. Clicking on the scrollbar on either side of the thumb will move +//! the thumb a step in that direction. The scrollbar is hidden by default and will show when it is +//! animated, dragged or approached by the cursor. +//! +//! The scrollbar has a horizontal orientation with the beginning on the left and the end on the +//! right. But it can be rotated arbitrarily. The origin is in the center. +//! +//! All operations related to the scroll position take as argument a number of pixels describing a +//! position or distance on the scrolled area. We call them scroll units. use crate::prelude::*; @@ -13,20 +23,26 @@ use ensogl_theme as theme; use crate::component; use crate::selector::Bounds; -use crate::selector::bounds::absolute_value; -use crate::selector::bounds::normalise_value; -use crate::selector::bounds::should_clamp_with_overflow; use crate::selector::model::Model; use crate::selector; -use ensogl_core::display::object::ObjectOps; +use ensogl_core::animation::delayed::DelayedAnimation; + // ================= // === Constants === // ================= -/// Amount the scrollbar moves ona single click, relative to the viewport width. -const CLICK_JUMP_PERCENTAGE: f32 = 0.80; +/// Amount the scrollbar moves on a single click, relative to the viewport size. +const CLICK_JUMP_PERCENTAGE : f32 = 0.80; +/// Width of the scrollbar in px. +pub const WIDTH : f32 = 11.0; +/// The amount of padding on each side inside the scrollbar. +const PADDING : f32 = 2.0; +/// The thumb will be displayed with at least this size to make it more visible and dragging easier. +const MIN_THUMB_SIZE : f32 = 12.0; +/// After an animation, the thumb will be visible for this time, before it hides again. +const HIDE_DELAY : f32 = 1000.0; @@ -36,128 +52,208 @@ const CLICK_JUMP_PERCENTAGE: f32 = 0.80; ensogl_core::define_endpoints! { Input { - resize(Vector2), - - set_track(Bounds), - set_overall_bounds(Bounds), - - set_track_color(color::Rgba), + /// Sets the length of the scrollbar as display object in px. + set_length (f32), + /// Sets the number of scroll units on the scroll bar. Should usually be the size of the + /// scrolled area in px. + set_max (f32), + /// Sets the thumb size in scroll units. + set_thumb_size (f32), + + /// Scroll smoothly by the given amount in scroll units. + scroll_by (f32), + /// Scroll smoothly to the given position in scroll units. + scroll_to (f32), + /// Jumps to the given position in scroll units without animation and without revealing the + /// scrollbar. + jump_to (f32), } Output { - track(Bounds) + /// Scroll position in scroll units. + thumb_position (f32), } } -/// Returns the result of the projection of the given `vector` to the x-axis of the coordinate -/// system of the given `shape`. For example, if the vector is parallel to the x-axis of the shape -/// coordinate system, it is returned unchanged, if it is perpendicular the zero vector is returned. -fn vector_aligned_with_object(vector:&Vector2,object:impl ObjectOps) -> Vector2 { - let object_rotation = Rotation2::new(-object.rotation().z); - object_rotation * vector -} - 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 track_position_lower = Animation::new(&network); - let track_position_upper = Animation::new(&network); + let frp = &self; + let network = &frp.network; + let scene = app.display.scene(); + let mouse = &scene.mouse.frp; + let thumb_position = Animation::new(network); + let thumb_color = color::Animation::new(network); + let activity_cool_off = DelayedAnimation::new(network); + activity_cool_off.frp.set_delay(HIDE_DELAY); + activity_cool_off.frp.set_duration(0.0); + + frp::extend! { network + resize <- frp.set_length.map(|&length| Vector2::new(length,WIDTH)); + } - let base_frp = selector::Frp::new(model, style, network, frp.resize.clone().into(), mouse); + let base_frp = selector::Frp::new(model, style, network, resize.clone().into(), mouse); model.use_track_handles(false); model.set_track_corner_round(true); model.show_background(false); model.show_left_overflow(false); model.show_right_overflow(false); + model.set_padding(PADDING); - let style_track_color = style.get_color(theme::component::slider::track::color); + let default_color = style.get_color(theme::component::slider::track::color); + let hover_color = style.get_color(theme::component::slider::track::hover_color); frp::extend! { network - // Simple Inputs - eval frp.set_track_color((value) model.set_track_color(*value)); - - // API - `set_track` - track_position_lower.target <+ frp.set_track.map(|b| b.start); - track_position_upper.target <+ frp.set_track.map(|b| b.end); - track_position_lower.skip <+ frp.set_track.constant(()); - track_position_upper.skip <+ frp.set_track.constant(()); - - // Normalise values for internal use. - normalised_track_bounds <- all2(&frp.track,&frp.set_overall_bounds).map(|(track,overall)|{ - Bounds::new(normalise_value(&(track.start,*overall)),normalise_value(&(track.end,*overall))) - }); - normalised_track_center <- normalised_track_bounds.map(|bounds| bounds.center()); - normalised_track_width <- normalised_track_bounds.map(|bounds| bounds.width()); - // Slider Updates - update_slider <- all(&normalised_track_bounds,&frp.resize); - eval update_slider(((value,size)) model.set_background_range(*value,*size)); + // Scrolling and Jumping + + frp.scroll_to <+ frp.scroll_by.map2(&thumb_position.target,|delta,pos| *pos+*delta); + + // We will use this to reveal the scrollbar on scrolling. It has to be defined before + // the following nodes that update `thumb_position.target`. + active <- frp.scroll_to.map2(&thumb_position.target,|&new,&old| new != old).on_true(); + + unbounded_target_position <- any(&frp.scroll_to,&frp.jump_to); + thumb_position.target <+ all_with3(&unbounded_target_position,&frp.set_thumb_size, + &frp.set_max,|target,&size,&max| target.min(max-size).max(0.0)); + thumb_position.skip <+ frp.jump_to.constant(()); + frp.source.thumb_position <+ thumb_position.value; + - // Mouse IO - Clicking - click_delta <- base_frp.background_click.map3(&normalised_track_center,&normalised_track_width, - f!([model](click_value,current_value,track_width) { - let shape_aligned = vector_aligned_with_object(click_value,&model); - let direction = if shape_aligned.x > *current_value { 1.0 } else { -1.0 }; - direction * track_width * CLICK_JUMP_PERCENTAGE - })); - click_animation <- click_delta.constant(true); + // === Mouse position in local coordinates === - // Mouse IO - Dragging - drag_movement <- mouse.translation.gate(&base_frp.is_dragging_any); - drag_delta <- drag_movement.map2(&base_frp.track_max_width, f!([model](delta,width) { - let shape_aligned = vector_aligned_with_object(delta,&model); - (shape_aligned.x) / width - })); - drag_delta <- drag_delta.gate(&base_frp.is_dragging_track); - drag_animation <- drag_delta.constant(false); + mouse_position <- mouse.position.map(f!([scene,model](pos) + scene.screen_to_object_space(&model,*pos))); - // Mouse IO - Event Evaluation - should_animate <- any(&click_animation,&drag_animation); + // We will initialize the mouse position with `Vector2(f32::NAN,f32::NAN)`, because the + // default `Vector2(0.0,0.0)` would reveal the scrollbar before we get the actual mouse + // coordinates. + init_mouse_position <- source::(); + mouse_position <- any(&mouse_position,&init_mouse_position); - drag_center_delta <- any(&drag_delta,&click_delta); - drag_update <- drag_center_delta.map2(&normalised_track_bounds,|delta,Bounds{start,end}| - Bounds::new(start+delta,end+delta) - ); - is_in_bounds <- drag_update.map(|value| should_clamp_with_overflow(value,&None)); + // === Color === - new_value_absolute <- all(&frp.set_overall_bounds,&drag_update).map(|(bounds,Bounds{start,end})| - Bounds::new( - absolute_value(&(*bounds,*start)),absolute_value(&(*bounds,*end))).sorted() - ).gate(&is_in_bounds); + init_color <- any_mut::<()>(); + default_color <- all(&default_color,&init_color)._0().map(|c| color::Lch::from(*c)); + hover_color <- all(&hover_color,&init_color)._0().map(|c| color::Lch::from(*c)); - new_value_lower <- new_value_absolute.map(|b| b.start); - new_value_upper <- new_value_absolute.map(|b| b.end); + engaged <- base_frp.track_hover || base_frp.is_dragging_track; + thumb_color.target_color <+ engaged.switch(&default_color,&hover_color); + eval thumb_color.value((c) model.set_track_color(color::Rgba::from(*c))); - track_position_lower.target <+ new_value_lower.gate(&is_in_bounds); - track_position_upper.target <+ new_value_upper.gate(&is_in_bounds); - track_position_lower.skip <+ should_animate.on_false(); - track_position_upper.skip <+ should_animate.on_false(); + // === Hiding === - track_position <- all(&track_position_lower.value,&track_position_upper.value).map( - |(lower,upper)| Bounds::new(*lower,*upper)); + // We start a delayed animation whenever the bar is scrolled to a new place (it is + // active). This will instantly reveal the scrollbar and hide it after the delay has + // passed. + activity_cool_off.frp.reset <+ active; + activity_cool_off.frp.start <+ active; - frp.source.track <+ track_position; + recently_active <- bool(&activity_cool_off.frp.on_end,&activity_cool_off.frp.on_reset); + + // The signed distance between the cursor and the edge of the scrollbar. If the cursor + // is further left or right than the ends of the scrollbar then we count the distance as + // infinite. + vert_mouse_distance <- all_with(&mouse_position,&frp.set_length,|&pos,&length| { + let scrollbar_x_range = (-length/2.0)..(length/2.0); + if scrollbar_x_range.contains(&pos.x) { + pos.y.abs() - WIDTH / 2.0 + } else { + f32::INFINITY + } + }); + + thumb_color.target_alpha <+ all_with5(&recently_active,&base_frp.is_dragging_track, + &vert_mouse_distance,&frp.set_thumb_size,&frp.set_max, + |&active,&dragging,&dist,&thumb_size,&max| { + if active || dragging { + 1.0 + } else if thumb_size < max { // If there is any space to scroll + if dist <= 0.0 { + 1.0 + } else { + (0.7 - dist / 20.0).max(0.0).min(1.0) + } + } else { + 0.0 + } + }); + + + // === Position on Screen === + + // Space that the thumb can actually move in + inner_length <- frp.set_length.map(|length| *length - 2.0 * PADDING); + // Thumb position as a number between 0 and 1 + normalized_position <- all_with3(&frp.thumb_position,&frp.set_thumb_size,&frp.set_max, + |&pos,&size,&max| pos / (max - size)); + normalized_size <- all_with(&frp.set_thumb_size,&frp.set_max,|&size,&max| + size / max); + // Minimum thumb size in normalized units + min_visual_size <- inner_length.map(|&length| MIN_THUMB_SIZE / length); + // The size at which we render the thumb on screen, in normalized units. Can differ from + // the actual thumb size if the thumb is smaller than the min. + visual_size <- all_with(&normalized_size,&min_visual_size,|&size,&min| + size.max(min).min(1.0)); + // The position at which we render the thumb on screen, in normalized units. + visual_start <- all_with(&normalized_position,&visual_size,|&pos,&size| + pos * (1.0 - size)); + visual_bounds <- all_with(&visual_start,&visual_size,|&start,&size| + Bounds::new(start,start+size)); + visual_center <- visual_bounds.map(|bounds| bounds.center()); + thumb_center_px <- all_with(&visual_center,&inner_length, |normalized,length| + (normalized - 0.5) * length); + + update_slider <- all(&visual_bounds,&resize); + eval update_slider(((value,size)) model.set_background_range(*value,*size)); + + + // === Clicking === + + frp.scroll_by <+ base_frp.background_click.map3(&thumb_center_px,&frp.set_thumb_size, + |click_position,thumb_center,thumb_size| { + let direction = if click_position.x > *thumb_center { 1.0 } else { -1.0 }; + direction * thumb_size * CLICK_JUMP_PERCENTAGE + }); + + + // === Dragging === + + drag_started <- base_frp.is_dragging_track.on_change().on_true().constant(()); + drag_offset <- all4(&mouse_position,&inner_length,&frp.set_max,&frp.thumb_position) + .sample(&drag_started) + .map(|(mouse_px,length_px,max,thumb_pos)| { + let thumb_position_px = thumb_pos / max * length_px; + mouse_px.x - thumb_position_px + }); + frp.jump_to <+ all4(&mouse_position,&drag_offset,&inner_length,&frp.set_max) + .gate(&base_frp.is_dragging_track) + .map(|(mouse_px,offset_px,length_px,max)| { + let target_px = mouse_px.x - offset_px; + target_px / length_px * max + }); } - // Init defaults - frp.set_overall_bounds(Bounds::new(0.0,1.0)); - frp.set_track(Bounds::new(0.25,0.55)); - frp.set_track_color(style_track_color.value()); + + // === Init Network === + + frp.set_length(200.0); + frp.set_thumb_size(0.2); + frp.set_max(1.0); + init_mouse_position.emit(Vector2(f32::NAN,f32::NAN)); + init_color.emit(()); } } -// ================= -// === Scrollbar === -// ================= +// =========================== +// === Scrollbar Component === +// =========================== -/// Scrollbar component that can be used to implement scrollable components. +/// Scrollbar component that can be used to implement scrollable components. See module description +/// for details. pub type Scrollbar = crate::component::Component; impl application::View for Scrollbar { diff --git a/src/rust/ensogl/lib/components/src/selector/frp.rs b/src/rust/ensogl/lib/components/src/selector/frp.rs index 0a3041a4cf..5fef4f4ed3 100644 --- a/src/rust/ensogl/lib/components/src/selector/frp.rs +++ b/src/rust/ensogl/lib/components/src/selector/frp.rs @@ -86,15 +86,10 @@ impl Frp { let is_dragging_right_handle = shape_is_dragged( network,&model.track_handle_right.events,mouse); - let model_fn = model.clone_ref(); - let base_position = move || model_fn.position().xy(); let background_click = relative_shape_down_position( - base_position,network,&model.background.events,mouse); - - let model_fn = model.clone_ref(); - let base_position = move || model_fn.position().xy(); - let track_click = relative_shape_down_position( - base_position,network,&model.track.events,mouse); + network,model.app.display.scene(),&model.background); + let track_click = relative_shape_down_position( + network,model.app.display.scene(),&model.track); // Initialisation of components. Required for correct layout on startup. model.label_right.set_position_y(text_size.value()/2.0); @@ -103,13 +98,14 @@ impl Frp { model.caption_left.set_position_y(text_size.value()/2.0); model.caption_center.set_position_y(text_size.value()/2.0); - let bg_color = style.get_color(theme::component::slider::background); + let bg_color = style.get_color(theme::component::slider::background); model.set_background_color(bg_color.value()); frp::extend! { network // Style updates. - shadow_padding <- shadow.size.map(|&v| Vector2(v,v)); + init_shadow_padding <- source::<()>(); + shadow_padding <- all_with(&shadow.size,&init_shadow_padding,|&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); @@ -144,6 +140,8 @@ impl Frp { track_hover <- bool(&model.track.events.mouse_out,&model.track.events.mouse_over); } + init_shadow_padding.emit(()); + 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,background_click,track_click,track_hover} diff --git a/src/rust/ensogl/lib/components/src/selector/model.rs b/src/rust/ensogl/lib/components/src/selector/model.rs index f28ec96841..4a976f4826 100644 --- a/src/rust/ensogl/lib/components/src/selector/model.rs +++ b/src/rust/ensogl/lib/components/src/selector/model.rs @@ -69,6 +69,9 @@ pub struct Model { track_color : Rc>, background_left_corner_roundness : Rc>, background_right_corner_roundness : Rc>, + padding : Rc>, + + pub app : Application, } impl component::Model for Model { @@ -90,6 +93,7 @@ impl component::Model for Model { let track_color = default(); let background_left_corner_roundness = default(); let background_right_corner_roundness = default(); + let padding = default(); let app = app.clone_ref(); let scene = app.display.scene(); @@ -118,7 +122,8 @@ impl component::Model for Model { Self{background,track,track_handle_left,track_handle_right,left_overflow,right_overflow, label,label_left,label_right,caption_left,caption_center,root,background_color, - track_color,background_left_corner_roundness,background_right_corner_roundness} + track_color,background_left_corner_roundness,background_right_corner_roundness,padding, + app} } } @@ -126,11 +131,12 @@ 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); - self.track.size.set(padded_size); - self.left_overflow.size.set(padded_size); - self.right_overflow.size.set(padded_size); + let size_with_shadow = size + shadow_padding; + self.background.size.set(size_with_shadow); + self.left_overflow.size.set(size_with_shadow); + self.right_overflow.size.set(size_with_shadow); + let padding = Vector2(self.padding.get()*2.0,self.padding.get()*2.0); + self.track.size.set(size_with_shadow-padding); let left_padding = LABEL_OFFSET; let overflow_icon_size = size.y; @@ -260,6 +266,10 @@ impl Model { self.track.corner_inner.set(corner_roundness) } + pub fn set_padding(&self, padding:f32) { + self.padding.set(padding); + } + pub fn show_background(&self, value:bool) { if value { self.background.show_shadow.set(1.0); diff --git a/src/rust/ensogl/lib/components/src/selector/number.rs b/src/rust/ensogl/lib/components/src/selector/number.rs index c4a8ecbf70..5a37cede47 100644 --- a/src/rust/ensogl/lib/components/src/selector/number.rs +++ b/src/rust/ensogl/lib/components/src/selector/number.rs @@ -56,15 +56,10 @@ 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_down_position( - base_position,network,&model.background.events,mouse); - - let madel_fn = model.clone_ref(); - let base_position = move || madel_fn.position().xy(); + network,model.app.display.scene(),&model.background); let track_click = relative_shape_down_position( - base_position,network,&model.track.events,mouse); + network,model.app.display.scene(),&model.track); let style_track_color = style.get_color(theme::component::slider::track::color); diff --git a/src/rust/ensogl/lib/components/src/selector/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs index f72014cb58..1fee768afd 100644 --- a/src/rust/ensogl/lib/components/src/selector/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -190,10 +190,13 @@ pub mod right_overflow { use enso_frp; use enso_frp::Network; use ensogl_core::frp::io::Mouse; +use ensogl_core::gui::component::ShapeView; use ensogl_core::gui::component::ShapeViewEvents; pub use super::frp::*; pub use super::model::*; +use ensogl_core::display; +use ensogl_core::display::Scene; /// 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. @@ -210,21 +213,22 @@ pub fn shape_is_dragged 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_down_position( - base_position:impl Fn() -> Vector2 + 'static, - network:&Network, - shape:&ShapeViewEvents, - mouse:&Mouse) -> enso_frp::Stream { +/// Returns the position of a mouse down on a shape. The position is given in the shape's local +/// coordinate system +pub fn relative_shape_down_position +( network : &Network +, scene : &Scene +, shape : &ShapeView +) -> enso_frp::Stream { + let mouse = &scene.mouse.frp; enso_frp::extend! { network mouse_down <- mouse.down.constant(()); - over_shape <- bool(&shape.mouse_out,&shape.mouse_over); + over_shape <- bool(&shape.events.mouse_out,&shape.events.mouse_over); mouse_down_over_shape <- mouse_down.gate(&over_shape); click_positon <- mouse.position.sample(&mouse_down_over_shape); - click_positon <- click_positon.map(move |pos| - pos - base_position() - ); + click_positon <- click_positon.map(f!([scene,shape](pos) + scene.screen_to_object_space(&shape,*pos) + )); } click_positon } diff --git a/src/rust/ensogl/lib/theme/src/lib.rs b/src/rust/ensogl/lib/theme/src/lib.rs index e1adc3cdd7..bb8bfd356d 100644 --- a/src/rust/ensogl/lib/theme/src/lib.rs +++ b/src/rust/ensogl/lib/theme/src/lib.rs @@ -401,7 +401,8 @@ define_themes! { [light:0, dark:1] 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); + color = Lcha(0.7,0.0,0.0,1.0), Lcha(0.3,0.0,0.0,1.0); + hover_color = Lcha(0.6,0.0,0.0,1.0), Lcha(0.4,0.0,0.0,1.0); } overflow { color = Lcha(0.0,0.0,0.0,1.0), Lcha(1.0,0.0,0.0,1.0); From 406773dcd90f0f8883b927407cded6050a0f8c4e Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Wed, 23 Jun 2021 09:51:09 +0100 Subject: [PATCH 04/11] Resolve clippy warnings --- src/rust/ensogl/lib/components/src/scrollbar.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 273e2e6521..02c5a81395 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -90,7 +90,7 @@ impl component::Frp for Frp { resize <- frp.set_length.map(|&length| Vector2::new(length,WIDTH)); } - let base_frp = selector::Frp::new(model, style, network, resize.clone().into(), mouse); + let base_frp = selector::Frp::new(model, style, network, resize.clone(), mouse); model.use_track_handles(false); model.set_track_corner_round(true); @@ -110,7 +110,10 @@ impl component::Frp for Frp { // We will use this to reveal the scrollbar on scrolling. It has to be defined before // the following nodes that update `thumb_position.target`. - active <- frp.scroll_to.map2(&thumb_position.target,|&new,&old| new != old).on_true(); + active <- frp.scroll_to.map2(&thumb_position.target,|&new:&f32,&old:&f32| { + let error_margin = 0.1; + (new - old).abs() > error_margin + }).on_true(); unbounded_target_position <- any(&frp.scroll_to,&frp.jump_to); thumb_position.target <+ all_with3(&unbounded_target_position,&frp.set_thumb_size, From bdf2e54d37b68a2e3c19995d3bea8287c870f77f Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Wed, 23 Jun 2021 10:52:18 +0100 Subject: [PATCH 05/11] Use inclusive ranges where appropriate --- src/rust/ensogl/lib/components/src/scroll_area.rs | 2 +- src/rust/ensogl/lib/components/src/scrollbar.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/scroll_area.rs b/src/rust/ensogl/lib/components/src/scroll_area.rs index 57b1926caf..744440c4d7 100644 --- a/src/rust/ensogl/lib/components/src/scroll_area.rs +++ b/src/rust/ensogl/lib/components/src/scroll_area.rs @@ -141,7 +141,7 @@ impl ScrollArea { hovering <- all_with(&mouse.frp.position,&frp.resize, f!([scene,display_object](&pos,&size) { let local_pos = scene.screen_to_object_space(&display_object,pos); - (0.0..size.x).contains(&local_pos.x) && (-size.y..0.0).contains(&local_pos.y) + (0.0..=size.x).contains(&local_pos.x) && (-size.y..=0.0).contains(&local_pos.y) })); hovering <- hovering.sampler(); } diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 02c5a81395..07efb80f9e 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -159,7 +159,7 @@ impl component::Frp for Frp { // is further left or right than the ends of the scrollbar then we count the distance as // infinite. vert_mouse_distance <- all_with(&mouse_position,&frp.set_length,|&pos,&length| { - let scrollbar_x_range = (-length/2.0)..(length/2.0); + let scrollbar_x_range = (-length/2.0)..=(length/2.0); if scrollbar_x_range.contains(&pos.x) { pos.y.abs() - WIDTH / 2.0 } else { From 755274db822d694b345014d56703647a7057b404 Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Thu, 24 Jun 2021 19:42:34 +0100 Subject: [PATCH 06/11] Don't reveal scrollbar when there is no space to scroll --- src/rust/ensogl/lib/components/src/scrollbar.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 07efb80f9e..002a887896 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -170,16 +170,17 @@ impl component::Frp for Frp { thumb_color.target_alpha <+ all_with5(&recently_active,&base_frp.is_dragging_track, &vert_mouse_distance,&frp.set_thumb_size,&frp.set_max, |&active,&dragging,&dist,&thumb_size,&max| { - if active || dragging { + let thumb_fills_bar = thumb_size >= max; + if thumb_fills_bar { + 0.0 + } else if active || dragging { 1.0 - } else if thumb_size < max { // If there is any space to scroll + } else { if dist <= 0.0 { 1.0 } else { (0.7 - dist / 20.0).max(0.0).min(1.0) } - } else { - 0.0 } }); From 8f630bf403a7646e9e37b1c041d42c33d701ffcd Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Fri, 25 Jun 2021 18:48:15 +0100 Subject: [PATCH 07/11] Make requested changes --- src/rust/ensogl/example/src/slider.rs | 1 + .../ensogl/lib/components/src/scroll_area.rs | 13 ++-- .../ensogl/lib/components/src/scrollbar.rs | 59 +++++++++++-------- .../lib/components/src/selector/shape.rs | 2 +- 4 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/rust/ensogl/example/src/slider.rs b/src/rust/ensogl/example/src/slider.rs index 822f305ed4..72eac2af88 100644 --- a/src/rust/ensogl/example/src/slider.rs +++ b/src/rust/ensogl/example/src/slider.rs @@ -13,6 +13,7 @@ use ensogl_text_msdf_sys::run_once_initialized; use ensogl_theme as theme; + // =================== // === Entry Point === // =================== diff --git a/src/rust/ensogl/lib/components/src/scroll_area.rs b/src/rust/ensogl/lib/components/src/scroll_area.rs index 744440c4d7..114b996486 100644 --- a/src/rust/ensogl/lib/components/src/scroll_area.rs +++ b/src/rust/ensogl/lib/components/src/scroll_area.rs @@ -1,8 +1,4 @@ -//! This module provides the [`ScrollArea`] component. It displays two scrollbars, for horizontal -//! and vertical scrolling. Content can be added to the `content` attribute. The content size has -//! to be set through `set_content_height` and `set_content_width`. The component is anchored at -//! the top left corner. All scroll coordinates describe the point of the `content` object at that -//! corner. +//! This module provides the [`ScrollArea`] component. use crate::prelude::*; @@ -53,7 +49,12 @@ ensogl_core::define_endpoints! { // === Scroll Area === // =================== -/// See module description +/// This struct provides a scroll area component. It displays two scrollbars, for horizontal and +/// vertical scrolling. Content can be added to the `content` attribute. The content size has to be +/// set through `set_content_height` and `set_content_width`. The component is anchored at the top +/// left corner. All scroll coordinates describe the point of the `content` object at that corner. +/// The scrollbars are only active when the content is actually larger than the viewport on the +/// respective axis. #[derive(Debug,Clone,CloneRef)] pub struct ScrollArea { /// All objects that should be inside the scroll area and affected by the scrolling, have to be diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 002a887896..ebac55e97a 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -44,6 +44,8 @@ const MIN_THUMB_SIZE : f32 = 12.0; /// After an animation, the thumb will be visible for this time, before it hides again. const HIDE_DELAY : f32 = 1000.0; +const ERROR_MARGIN_FOR_ACTIVITY_DETECTION : f32 = 0.1; + // =========== @@ -74,6 +76,27 @@ ensogl_core::define_endpoints! { } } +impl Frp { + fn compute_target_alpha + (&recently_active:&bool, &dragging:&bool, &cursor_distance:&f32, &thumb_size:&f32, &max:&f32) + -> f32 { + let thumb_fills_bar = thumb_size >= max; + if thumb_fills_bar { + 0.0 + } else if recently_active || dragging { + 1.0 + } else { + if cursor_distance <= 0.0 { + 1.0 + } else { + // The opacity approaches 0.7 when the cursor is right next to the bar and fades + // linearly to 0.0 at 20 px distance. + (0.7 - cursor_distance / 20.0).max(0.0) + } + } + } +} + impl component::Frp for Frp { fn init(&self, app:&Application, model:&Model, style:&StyleWatchFrp){ let frp = &self; @@ -111,8 +134,7 @@ impl component::Frp for Frp { // We will use this to reveal the scrollbar on scrolling. It has to be defined before // the following nodes that update `thumb_position.target`. active <- frp.scroll_to.map2(&thumb_position.target,|&new:&f32,&old:&f32| { - let error_margin = 0.1; - (new - old).abs() > error_margin + (new - old).abs() > ERROR_MARGIN_FOR_ACTIVITY_DETECTION }).on_true(); unbounded_target_position <- any(&frp.scroll_to,&frp.jump_to); @@ -157,7 +179,10 @@ impl component::Frp for Frp { // The signed distance between the cursor and the edge of the scrollbar. If the cursor // is further left or right than the ends of the scrollbar then we count the distance as - // infinite. + // infinite. We use this distance to reveal the scrollbar when approached by the cursor. + // Returning infinity has the effect that we do not reveal it when the cursor approaches + // from the sides. This could be handled differently, but the solution was chosen for + // the simplicity of the implementation and the feeling of the interaction. vert_mouse_distance <- all_with(&mouse_position,&frp.set_length,|&pos,&length| { let scrollbar_x_range = (-length/2.0)..=(length/2.0); if scrollbar_x_range.contains(&pos.x) { @@ -168,21 +193,7 @@ impl component::Frp for Frp { }); thumb_color.target_alpha <+ all_with5(&recently_active,&base_frp.is_dragging_track, - &vert_mouse_distance,&frp.set_thumb_size,&frp.set_max, - |&active,&dragging,&dist,&thumb_size,&max| { - let thumb_fills_bar = thumb_size >= max; - if thumb_fills_bar { - 0.0 - } else if active || dragging { - 1.0 - } else { - if dist <= 0.0 { - 1.0 - } else { - (0.7 - dist / 20.0).max(0.0).min(1.0) - } - } - }); + &vert_mouse_distance,&frp.set_thumb_size,&frp.set_max,Self::compute_target_alpha); // === Position on Screen === @@ -225,15 +236,15 @@ impl component::Frp for Frp { // === Dragging === drag_started <- base_frp.is_dragging_track.on_change().on_true().constant(()); - drag_offset <- all4(&mouse_position,&inner_length,&frp.set_max,&frp.thumb_position) - .sample(&drag_started) - .map(|(mouse_px,length_px,max,thumb_pos)| { + x <- all4(&mouse_position,&inner_length,&frp.set_max,&frp.thumb_position); + x <- x.sample(&drag_started); + drag_offset <- x.map(|(mouse_px,length_px,max,thumb_pos)| { let thumb_position_px = thumb_pos / max * length_px; mouse_px.x - thumb_position_px }); - frp.jump_to <+ all4(&mouse_position,&drag_offset,&inner_length,&frp.set_max) - .gate(&base_frp.is_dragging_track) - .map(|(mouse_px,offset_px,length_px,max)| { + x <- all4(&mouse_position,&drag_offset,&inner_length,&frp.set_max); + x <- x.gate(&base_frp.is_dragging_track); + frp.jump_to <+ x.map(|(mouse_px,offset_px,length_px,max)| { let target_px = mouse_px.x - offset_px; target_px / length_px * max }); diff --git a/src/rust/ensogl/lib/components/src/selector/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs index 1fee768afd..58c133e7a1 100644 --- a/src/rust/ensogl/lib/components/src/selector/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -54,7 +54,7 @@ pub mod background { (style:Style,corner_left:f32,corner_right:f32,color:Vector4,show_shadow:f32) { let background = Background::new(&corner_left,&corner_right,style); let shadow = shadow::from_shape_with_alpha(background.shape.clone(), - &show_shadow,style); + &show_shadow,style); let background = background.shape.fill(color); (shadow + background).into() } From 9aef6c9feed2e3b58b333834b30c99bf7a39f7ab Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Fri, 25 Jun 2021 18:51:46 +0100 Subject: [PATCH 08/11] Disable Clippy warning --- src/rust/ensogl/lib/components/src/scrollbar.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index ebac55e97a..4d7048cd8b 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -86,6 +86,7 @@ impl Frp { } else if recently_active || dragging { 1.0 } else { + #[allow(clippy::collapsible_else_if)] if cursor_distance <= 0.0 { 1.0 } else { From f207f513d35ae2fce985f96288355f0cb341734f Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Sat, 26 Jun 2021 13:58:20 +0100 Subject: [PATCH 09/11] Remove broken test It would likely take me long to fix this test because it now depends on the existence of a `Scene`. The test also not very important. Therefore, I decided to delete it. --- .../lib/components/src/selector/shape.rs | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/selector/shape.rs b/src/rust/ensogl/lib/components/src/selector/shape.rs index 58c133e7a1..2e28c0bab7 100644 --- a/src/rust/ensogl/lib/components/src/selector/shape.rs +++ b/src/rust/ensogl/lib/components/src/selector/shape.rs @@ -275,31 +275,4 @@ mod tests { 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_down_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 537e8afd55c247ce1b32084f727513240fe4004b Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Thu, 1 Jul 2021 11:28:26 +0100 Subject: [PATCH 10/11] Add todo to move constants to theme --- src/rust/ensogl/lib/components/src/scrollbar.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 4d7048cd8b..3efe0a4885 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -33,6 +33,10 @@ use ensogl_core::animation::delayed::DelayedAnimation; // === Constants === // ================= +// TODO: Some of those values could be defined by the theme instead. But currently, this does not +// seem to be worth it because the FRP initialization introduces a lot of complexity, as +// described at https://github.com/enso-org/ide/issues/1654. + /// Amount the scrollbar moves on a single click, relative to the viewport size. const CLICK_JUMP_PERCENTAGE : f32 = 0.80; /// Width of the scrollbar in px. From 274a72ad79bf588954473886fdc3ec92c76bf6fe Mon Sep 17 00:00:00 2001 From: Felix Rech Date: Fri, 2 Jul 2021 10:00:54 +0100 Subject: [PATCH 11/11] Move scrollbar documentation --- .../ensogl/lib/components/src/scrollbar.rs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/rust/ensogl/lib/components/src/scrollbar.rs b/src/rust/ensogl/lib/components/src/scrollbar.rs index 3efe0a4885..419706f6f9 100644 --- a/src/rust/ensogl/lib/components/src/scrollbar.rs +++ b/src/rust/ensogl/lib/components/src/scrollbar.rs @@ -1,14 +1,4 @@ -//! Module that contains a scrollbar component that can be used to implement scrollable components. -//! We say "thumb" to mean the object inside the bar that indicates the scroll position and can be -//! dragged to change that position. Clicking on the scrollbar on either side of the thumb will move -//! the thumb a step in that direction. The scrollbar is hidden by default and will show when it is -//! animated, dragged or approached by the cursor. -//! -//! The scrollbar has a horizontal orientation with the beginning on the left and the end on the -//! right. But it can be rotated arbitrarily. The origin is in the center. -//! -//! All operations related to the scroll position take as argument a number of pixels describing a -//! position or distance on the scrolled area. We call them scroll units. +//! Defines a scrollbar component. See definition of [`Scrollbar`] for details. use crate::prelude::*; @@ -272,8 +262,18 @@ impl component::Frp for Frp { // === Scrollbar Component === // =========================== -/// Scrollbar component that can be used to implement scrollable components. See module description -/// for details. +/// Scrollbar component that can be used to implement scrollable components. +/// +/// We say "thumb" to mean the object inside the bar that indicates the scroll position and can be +/// dragged to change that position. Clicking on the scrollbar on either side of the thumb will move +/// the thumb a step in that direction. The scrollbar is hidden by default and will show when it is +/// animated, dragged or approached by the cursor. +/// +/// The scrollbar has a horizontal orientation with the beginning on the left and the end on the +/// right. But it can be rotated arbitrarily. The origin is in the center. +/// +/// All operations related to the scroll position take as argument a number of pixels describing a +/// position or distance on the scrolled area. We call them scroll units. pub type Scrollbar = crate::component::Component; impl application::View for Scrollbar {