From 719c073fc67c87d6b2da1bc01b74751d3f5e59f0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 25 Oct 2019 03:47:34 +0200 Subject: Draft `Scrollable` widget (no clipping yet!) --- native/src/widget/scrollable.rs | 121 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 native/src/widget/scrollable.rs (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs new file mode 100644 index 00000000..4644282b --- /dev/null +++ b/native/src/widget/scrollable.rs @@ -0,0 +1,121 @@ +use crate::{ + column, input::mouse, Element, Event, Hasher, Layout, Node, Point, Style, + Widget, +}; + +pub use iced_core::scrollable::State; + +/// A scrollable [`Column`]. +/// +/// [`Column`]: ../column/struct.Column.html +pub type Scrollable<'a, Message, Renderer> = + iced_core::Scrollable<'a, Element<'a, Message, Renderer>>; + +impl<'a, Message, Renderer> Widget + for Scrollable<'a, Message, Renderer> +where + Renderer: self::Renderer + column::Renderer, +{ + fn node(&self, renderer: &Renderer) -> Node { + let mut content = self.content.node(renderer); + + { + let mut style = content.0.style(); + style.flex_shrink = 0.0; + + content.0.set_style(style); + } + + let mut style = Style::default() + .width(self.content.width) + .max_width(self.content.max_width) + .height(self.height) + .align_self(self.align_self) + .align_items(self.align_items); + + style.0.flex_direction = stretch::style::FlexDirection::Column; + + Node::with_children(style, vec![content]) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + ) { + let bounds = layout.bounds(); + let is_mouse_over = bounds.contains(cursor_position); + + let content = layout.children().next().unwrap(); + let content_bounds = content.bounds(); + + if is_mouse_over { + match event { + Event::Mouse(mouse::Event::WheelScrolled { + delta_y, .. + }) => { + // TODO: Configurable speed (?) + self.state.offset = (self.state.offset as i32 + - delta_y.round() as i32 * 15) + .max(0) + .min((content_bounds.height - bounds.height) as i32) + as u32; + } + _ => {} + } + } + + let cursor_position = if is_mouse_over { + Point::new( + cursor_position.x, + cursor_position.y + self.state.offset as f32, + ) + } else { + Point::new(cursor_position.x, -1.0) + }; + + self.content.on_event( + event, + layout.children().next().unwrap(), + cursor_position, + messages, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self::Renderer::draw(renderer, &self, layout, cursor_position) + } + + fn hash_layout(&self, state: &mut Hasher) { + self.content.hash_layout(state) + } +} + +pub trait Renderer: crate::Renderer + Sized { + fn draw( + &mut self, + scrollable: &Scrollable<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: 'a + self::Renderer + column::Renderer, + Message: 'static, +{ + fn from( + scrollable: Scrollable<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(scrollable) + } +} -- cgit From 09bd2c46c06eba72da40852d82a52e7353cc9e9b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 27 Oct 2019 01:24:08 +0200 Subject: Expose scrollable offset properly --- native/src/widget/scrollable.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 4644282b..f411915b 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -56,12 +56,7 @@ where Event::Mouse(mouse::Event::WheelScrolled { delta_y, .. }) => { - // TODO: Configurable speed (?) - self.state.offset = (self.state.offset as i32 - - delta_y.round() as i32 * 15) - .max(0) - .min((content_bounds.height - bounds.height) as i32) - as u32; + self.state.scroll(delta_y, bounds, content_bounds); } _ => {} } @@ -70,7 +65,8 @@ where let cursor_position = if is_mouse_over { Point::new( cursor_position.x, - cursor_position.y + self.state.offset as f32, + cursor_position.y + + self.state.offset(bounds, content_bounds) as f32, ) } else { Point::new(cursor_position.x, -1.0) -- cgit From 63c10b67ab213c5971313743fde566bd5c0f0c15 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 27 Oct 2019 01:37:40 +0200 Subject: Remove `Scrollable::justify_content` method --- native/src/widget/scrollable.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index f411915b..e0983e6e 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -30,6 +30,7 @@ where .width(self.content.width) .max_width(self.content.max_width) .height(self.height) + .max_height(self.max_height) .align_self(self.align_self) .align_items(self.align_items); -- cgit From 82c2aa6bfd1ed90b32b303a900e13b2c07bc69ba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 27 Oct 2019 02:59:25 +0100 Subject: Align items properly inside a `Scrollable` --- native/src/widget/scrollable.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index e0983e6e..95e8c74d 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -31,8 +31,7 @@ where .max_width(self.content.max_width) .height(self.height) .max_height(self.max_height) - .align_self(self.align_self) - .align_items(self.align_items); + .align_self(self.align_self); style.0.flex_direction = stretch::style::FlexDirection::Column; -- cgit From a3c55f75174f9bc87f80d7fe6236a71138d2fd77 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 29 Oct 2019 02:13:22 +0100 Subject: Stop leaking impl details in scrollable `Renderer` --- native/src/widget/scrollable.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 95e8c74d..f9d75863 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,6 +1,6 @@ use crate::{ - column, input::mouse, Element, Event, Hasher, Layout, Node, Point, Style, - Widget, + column, input::mouse, Element, Event, Hasher, Layout, Node, Point, + Rectangle, Style, Widget, }; pub use iced_core::scrollable::State; @@ -86,7 +86,16 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - self::Renderer::draw(renderer, &self, layout, cursor_position) + let bounds = layout.bounds(); + let content_layout = layout.children().next().unwrap(); + + self::Renderer::draw( + renderer, + &self, + bounds, + content_layout, + cursor_position, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -98,7 +107,8 @@ pub trait Renderer: crate::Renderer + Sized { fn draw( &mut self, scrollable: &Scrollable<'_, Message, Self>, - layout: Layout<'_>, + bounds: Rectangle, + content_layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; } -- cgit From 6602c1517cbffbc9ff0b6052ce7288cd51eb1e67 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 29 Oct 2019 03:29:29 +0100 Subject: Complete `Scrollable::hash_layout` --- native/src/widget/scrollable.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index f9d75863..9acba3c2 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -5,6 +5,8 @@ use crate::{ pub use iced_core::scrollable::State; +use std::hash::Hash; + /// A scrollable [`Column`]. /// /// [`Column`]: ../column/struct.Column.html @@ -69,15 +71,15 @@ where + self.state.offset(bounds, content_bounds) as f32, ) } else { + // TODO: Make `cursor_position` an `Option` so we can encode + // cursor unavailability. + // This will probably happen naturally once we add multi-window + // support. Point::new(cursor_position.x, -1.0) }; - self.content.on_event( - event, - layout.children().next().unwrap(), - cursor_position, - messages, - ) + self.content + .on_event(event, content, cursor_position, messages) } fn draw( @@ -99,6 +101,12 @@ where } fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::>().hash(state); + + self.height.hash(state); + self.max_height.hash(state); + self.align_self.hash(state); + self.content.hash_layout(state) } } -- cgit From 9dabbf78857c3a60583227d3aa2fa6e030f085d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 29 Oct 2019 03:34:21 +0100 Subject: Provide `Renderer` to `Widget::on_event` This allows us to implement configurable event processing that adapts to different rendering strategies. --- native/src/widget/scrollable.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 9acba3c2..e52f3c3f 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -46,6 +46,7 @@ where layout: Layout<'_>, cursor_position: Point, messages: &mut Vec, + renderer: &Renderer, ) { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); @@ -78,8 +79,13 @@ where Point::new(cursor_position.x, -1.0) }; - self.content - .on_event(event, content, cursor_position, messages) + self.content.on_event( + event, + content, + cursor_position, + messages, + renderer, + ) } fn draw( -- cgit From 29588f604af66fb4911f791c0c402fccd30ba64b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 29 Oct 2019 05:09:54 +0100 Subject: Implement scrollbar interactions! :tada: --- native/src/widget/scrollable.rs | 65 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index e52f3c3f..76d12124 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,6 +1,7 @@ use crate::{ - column, input::mouse, Element, Event, Hasher, Layout, Node, Point, - Rectangle, Style, Widget, + column, + input::{mouse, ButtonState}, + Element, Event, Hasher, Layout, Node, Point, Rectangle, Style, Widget, }; pub use iced_core::scrollable::State; @@ -54,18 +55,65 @@ where let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); + let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( + bounds, + content_bounds, + cursor_position, + ); + + // TODO: Event capture. Nested scrollables should capture scroll events. if is_mouse_over { match event { Event::Mouse(mouse::Event::WheelScrolled { delta_y, .. }) => { - self.state.scroll(delta_y, bounds, content_bounds); + // TODO: Configurable speed (?) + self.state.scroll(delta_y * 15.0, bounds, content_bounds); + } + _ => {} + } + } + + if self.state.is_scrollbar_grabbed() || is_mouse_over_scrollbar { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state, + }) => match state { + ButtonState::Pressed => { + self.state.scroll_to( + cursor_position.y / (bounds.y + bounds.height), + bounds, + content_bounds, + ); + + self.state.scrollbar_grabbed_at = Some(cursor_position); + } + ButtonState::Released => { + self.state.scrollbar_grabbed_at = None; + } + }, + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + if let Some(scrollbar_grabbed_at) = + self.state.scrollbar_grabbed_at + { + self.state.scroll( + scrollbar_grabbed_at.y - cursor_position.y, + bounds, + content_bounds, + ); + + self.state.scrollbar_grabbed_at = Some(cursor_position); + } } _ => {} } } - let cursor_position = if is_mouse_over { + let cursor_position = if is_mouse_over + && !(is_mouse_over_scrollbar + || self.state.scrollbar_grabbed_at.is_some()) + { Point::new( cursor_position.x, cursor_position.y @@ -73,7 +121,7 @@ where ) } else { // TODO: Make `cursor_position` an `Option` so we can encode - // cursor unavailability. + // cursor availability. // This will probably happen naturally once we add multi-window // support. Point::new(cursor_position.x, -1.0) @@ -118,6 +166,13 @@ where } pub trait Renderer: crate::Renderer + Sized { + fn is_mouse_over_scrollbar( + &self, + bounds: Rectangle, + content_bounds: Rectangle, + cursor_position: Point, + ) -> bool; + fn draw( &mut self, scrollable: &Scrollable<'_, Message, Self>, -- cgit From bd5d871eb6630bc8f987d72c771032f878fb91b6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 29 Oct 2019 19:00:46 +0100 Subject: Handle touchpad scroll events --- native/src/widget/scrollable.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 76d12124..8a82be4f 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -64,11 +64,16 @@ where // TODO: Event capture. Nested scrollables should capture scroll events. if is_mouse_over { match event { - Event::Mouse(mouse::Event::WheelScrolled { - delta_y, .. - }) => { - // TODO: Configurable speed (?) - self.state.scroll(delta_y * 15.0, bounds, content_bounds); + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + match delta { + mouse::ScrollDelta::Lines { y, .. } => { + // TODO: Configurable speed (?) + self.state.scroll(y * 15.0, bounds, content_bounds); + } + mouse::ScrollDelta::Pixels { y, .. } => { + self.state.scroll(y, bounds, content_bounds); + } + } } _ => {} } -- cgit From 85dab04965940f15cf0e9879e296f67235a3775c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 2 Nov 2019 01:46:45 +0100 Subject: Scale scrollbar movement by content ratio --- native/src/widget/scrollable.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'native/src/widget/scrollable.rs') diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 8a82be4f..de4c749c 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -102,8 +102,11 @@ where if let Some(scrollbar_grabbed_at) = self.state.scrollbar_grabbed_at { + let ratio = content_bounds.height / bounds.height; + let delta = scrollbar_grabbed_at.y - cursor_position.y; + self.state.scroll( - scrollbar_grabbed_at.y - cursor_position.y, + delta * ratio, bounds, content_bounds, ); -- cgit