Skip to content
This repository has been archived by the owner on Dec 28, 2021. It is now read-only.

Scrollbar and ScrollArea #1614

Merged
merged 12 commits into from
Jul 5, 2021
21 changes: 21 additions & 0 deletions src/rust/ensogl/example/src/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -45,6 +46,13 @@ fn make_range_picker(app:&Application) -> Leak<selector::NumberRangePicker> {
Leak::new(slider)
}

fn make_scrollbar(app:&Application) -> Leak<Scrollbar> {
let scrollbar = app.new_view::<Scrollbar>();
scrollbar.frp.resize(Vector2(200.0,50.0));
app.display.add_child(&scrollbar);
Leak::new(scrollbar)
}



// ========================
Expand Down Expand Up @@ -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));
}
3 changes: 2 additions & 1 deletion src/rust/ensogl/lib/components/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
152 changes: 152 additions & 0 deletions src/rust/ensogl/lib/components/src/scrollbar.rs
Original file line number Diff line number Diff line change
@@ -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<Model> 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);
s9ferech marked this conversation as resolved.
Show resolved Hide resolved

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);
s9ferech marked this conversation as resolved.
Show resolved Hide resolved
let rotated = rotation * click_value;
let direction = if rotated.x > *current_value { 1.0 } else { -1.0 };
const SCALE: f32 = 0.05;
s9ferech marked this conversation as resolved.
Show resolved Hide resolved
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();

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like pretty complex FRP - it seems it includes dragging etc. Why such things as dragging are not completely handled by slider instead? I was thinking that this component would just wrap slider and just set a few FRP values to it, without its own complex FRP utility. Also, there are no docs describing what really these functions do, which makes it much harder to understand - it contains some dragging, some mouse IO, while all of these things should be handled by the slider component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual drag implementation and underlying logic is shared. But this is the logic how to interpret it, which is different in each component: (1) single number selectors have clicking at the target and dragging only on the background (2) range slider need to act on dragging either the track piece or the edges of the track piece (3) the scrollbar needs to have dragging of the track piece and "jumping" when clicking on some other part of the background.
That is essentially what is encoded here. Maybe some things could be refactored and abstracted, but not too much.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why cant these things be just coded in (moved to) the slider implementation so you'd be able to choose which mode you wants with FRP settings? Just like choosing colors, we could tell "I want this slider to jump to the place when clicking the background". This should be part of the sldier configuration rather than extracted here imo. Can we move it there, please?

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<Model,Frp>;

impl application::View for Scrollbar {
fn label() -> &'static str { "Scrollbar" }
fn new(app:&Application) -> Self { Scrollbar::new(app) }
fn app(&self) -> &Application { &self.app }
}
7 changes: 4 additions & 3 deletions src/rust/ensogl/lib/components/src/selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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::*;



Expand Down
5 changes: 5 additions & 0 deletions src/rust/ensogl/lib/components/src/selector/bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 18 additions & 1 deletion src/rust/ensogl/lib/components/src/selector/frp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::shadow;

use super::model::Model;
use super::shape::shape_is_dragged;
use super::shape::relative_shape_down_position;



Expand Down Expand Up @@ -55,6 +56,12 @@ pub struct Frp {
pub is_dragging_right_handle : frp::Stream<bool>,
/// Indicates whether there is an ongoing dragging action on any of the component shapes.
pub is_dragging_any : frp::Stream<bool>,
/// 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<Vector2>,
/// 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<Vector2>,
}

impl Frp {
Expand All @@ -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);
Expand Down Expand Up @@ -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}
}
}
6 changes: 3 additions & 3 deletions src/rust/ensogl/lib/components/src/selector/number.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down Expand Up @@ -56,12 +56,12 @@ impl component::Frp<Model> 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);
Expand Down
16 changes: 8 additions & 8 deletions src/rust/ensogl/lib/components/src/selector/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vector2> {
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
}


Expand Down Expand Up @@ -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(());
Expand Down
3 changes: 3 additions & 0 deletions src/rust/lib/types/src/algebra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ mod vectors {
pub type Vector3<T=f32> = nalgebra::Vector3<T>;
pub type Vector4<T=f32> = nalgebra::Vector4<T>;

pub type Rotation2<T=f32> = nalgebra::Rotation2<T>;
pub type Rotation3<T=f32> = nalgebra::Rotation3<T>;

pub fn Vector2<T:Scalar>(t1:T,t2:T) -> Vector2<T> { Vector2::new(t1,t2) }
pub fn Vector3<T:Scalar>(t1:T,t2:T,t3:T) -> Vector3<T> { Vector3::new(t1,t2,t3) }
pub fn Vector4<T:Scalar>(t1:T,t2:T,t3:T,t4:T) -> Vector4<T> { Vector4::new(t1,t2,t3,t4) }
Expand Down