From 31e3b6fbcb06bd5e1e5773a7c2febd0cb0092819 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 3 Dec 2019 06:48:29 +0100 Subject: Unify logic by introducing `scrollable::Scrollbar` --- native/src/renderer/null.rs | 15 +-- native/src/widget/scrollable.rs | 215 ++++++++++++++++----------------- wgpu/src/renderer/widget/scrollable.rs | 110 +++++++++-------- 3 files changed, 169 insertions(+), 171 deletions(-) diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 2ce150c0..da0e5159 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -61,17 +61,13 @@ impl text::Renderer for Null { } impl scrollable::Renderer for Null { - fn scrollbar_bounds(_bounds: Rectangle) -> Rectangle { - Default::default() - } - - fn scroller_bounds( + fn scrollbar( + &self, _bounds: Rectangle, _content_bounds: Rectangle, - _scrollbar_bounds: Rectangle, _offset: u32, - ) -> Rectangle { - Default::default() + ) -> Option { + None } fn draw( @@ -81,8 +77,7 @@ impl scrollable::Renderer for Null { _content_bounds: Rectangle, _is_mouse_over: bool, _is_mouse_over_scrollbar: bool, - _scrollbar_bounds: Rectangle, - _scroller_bounds: Rectangle, + _scrollbar: Option, _offset: u32, _content: Self::Output, ) { diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index b48ae492..7753c2d2 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -102,17 +102,6 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { } } -fn scroll_percentage( - bounds: Rectangle, - scroller_bounds: Rectangle, - scroller_grabbed_at: f32, - cursor_position: Point, -) -> f32 { - (cursor_position.y + bounds.y - - scroller_bounds.height * scroller_grabbed_at) - / (bounds.height - scroller_bounds.height) -} - impl<'a, Message, Renderer> Widget for Scrollable<'a, Message, Renderer> where @@ -161,22 +150,6 @@ where let content = layout.children().next().unwrap(); let content_bounds = content.bounds(); - let offset = self.state.offset(bounds, content_bounds); - let scrollbar_bounds = Renderer::scrollbar_bounds(bounds); - let scroller_bounds = Renderer::scroller_bounds( - bounds, - content_bounds, - scrollbar_bounds, - offset, - ); - let scrollbar_grab = ScrollbarItem::from_cursor_position( - bounds, - content_bounds, - scrollbar_bounds, - scroller_bounds, - cursor_position, - ); - // TODO: Event capture. Nested scrollables should capture scroll events. if is_mouse_over { match event { @@ -195,48 +168,27 @@ where } } - if self.state.is_scroller_grabbed() || scrollbar_grab.is_some() { + let offset = self.state.offset(bounds, content_bounds); + let scrollbar = renderer.scrollbar(bounds, content_bounds, offset); + let is_mouse_over_scrollbar = scrollbar + .as_ref() + .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) + .unwrap_or(false); + + if self.state.is_scroller_grabbed() { match event { Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, - state, - }) => match state { - ButtonState::Pressed => { - let scroller_grabbed_at = match scrollbar_grab.unwrap() - { - ScrollbarItem::Background => 0.5, - ScrollbarItem::Scroller => { - (cursor_position.y - scroller_bounds.y) - / scroller_bounds.height - } - }; - - self.state.scroll_to( - scroll_percentage( - bounds, - scroller_bounds, - scroller_grabbed_at, - cursor_position, - ), - bounds, - content_bounds, - ); - - self.state.scroller_grabbed_at = - Some(scroller_grabbed_at); - } - ButtonState::Released => { - self.state.scroller_grabbed_at = None; - } - }, + state: ButtonState::Released, + }) => { + self.state.scroller_grabbed_at = None; + } Event::Mouse(mouse::Event::CursorMoved { .. }) => { - if let Some(scroller_grabbed_at) = - self.state.scroller_grabbed_at + if let (Some(scrollbar), Some(scroller_grabbed_at)) = + (scrollbar, self.state.scroller_grabbed_at) { self.state.scroll_to( - scroll_percentage( - bounds, - scroller_bounds, + scrollbar.scroll_percentage( scroller_grabbed_at, cursor_position, ), @@ -247,11 +199,35 @@ where } _ => {} } + } else if is_mouse_over_scrollbar { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Pressed, + }) => { + if let Some(scrollbar) = scrollbar { + if let Some(scroller_grabbed_at) = + scrollbar.grab_scroller(cursor_position) + { + self.state.scroll_to( + scrollbar.scroll_percentage( + scroller_grabbed_at, + cursor_position, + ), + bounds, + content_bounds, + ); + + self.state.scroller_grabbed_at = + Some(scroller_grabbed_at); + } + } + } + _ => {} + } } - let cursor_position = if is_mouse_over - && !(scrollbar_grab.is_some() || self.state.is_scroller_grabbed()) - { + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { Point::new( cursor_position.x, cursor_position.y @@ -284,23 +260,13 @@ where let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); let offset = self.state.offset(bounds, content_bounds); + let scrollbar = renderer.scrollbar(bounds, content_bounds, offset); let is_mouse_over = bounds.contains(cursor_position); - let scrollbar_bounds = Renderer::scrollbar_bounds(bounds); - let scroller_bounds = Renderer::scroller_bounds( - bounds, - content_bounds, - scrollbar_bounds, - offset, - ); - let is_mouse_over_scrollbar = ScrollbarItem::from_cursor_position( - bounds, - content_bounds, - scrollbar_bounds, - scroller_bounds, - cursor_position, - ) - .is_some(); + let is_mouse_over_scrollbar = scrollbar + .as_ref() + .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) + .unwrap_or(false); let content = { let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { @@ -319,8 +285,7 @@ where content_layout.bounds(), is_mouse_over, is_mouse_over_scrollbar, - scrollbar_bounds, - scroller_bounds, + scrollbar, offset, content, ) @@ -409,33 +374,60 @@ impl State { } } -#[derive(Debug, Clone, Copy)] -enum ScrollbarItem { - Background, - Scroller, +/// The scrollbar of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug)] +pub struct Scrollbar { + /// The bounds of the [`Scrollbar`]. + /// + /// [`Scrollbar`]: struct.Scrollbar.html + pub bounds: Rectangle, + + /// The bounds of the [`Scroller`]. + /// + /// [`Scroller`]: struct.Scroller.html + pub scroller: Scroller, } -impl ScrollbarItem { - /// `None` means the cursor is not over any item - fn from_cursor_position( - bounds: Rectangle, - content_bounds: Rectangle, - scrollbar_bounds: Rectangle, - scroller_bounds: Rectangle, - cursor_position: Point, - ) -> Option { - if content_bounds.height > bounds.height - && scrollbar_bounds.contains(cursor_position) - { - Some(if scroller_bounds.contains(cursor_position) { - ScrollbarItem::Scroller +impl Scrollbar { + fn is_mouse_over(&self, cursor_position: Point) -> bool { + self.bounds.contains(cursor_position) + } + + fn grab_scroller(&self, cursor_position: Point) -> Option { + if self.bounds.contains(cursor_position) { + Some(if self.scroller.bounds.contains(cursor_position) { + (cursor_position.y - self.scroller.bounds.y) + / self.scroller.bounds.height } else { - ScrollbarItem::Background + 0.5 }) } else { None } } + + fn scroll_percentage( + &self, + grabbed_at: f32, + cursor_position: Point, + ) -> f32 { + (cursor_position.y + self.bounds.y + - self.scroller.bounds.height * grabbed_at) + / (self.bounds.height - self.scroller.bounds.height) + } +} + +/// The handle of a [`Scrollbar`]. +/// +/// [`Scrollbar`]: struct.Scrollbar.html +#[derive(Debug, Clone, Copy)] +pub struct Scroller { + /// The bounds of the [`Scroller`]. + /// + /// [`Scroller`]: struct.Scrollbar.html + pub bounds: Rectangle, } /// The renderer of a [`Scrollable`]. @@ -446,17 +438,17 @@ impl ScrollbarItem { /// [`Scrollable`]: struct.Scrollable.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { - /// Returns the bounds of the scrollbar - fn scrollbar_bounds(bounds: Rectangle) -> Rectangle; - - /// Returns the bounds of the scroller - /// "The part that you can drag around with your mouse to scroll" - fn scroller_bounds( + /// Returns the [`Scrollbar`] given the bounds and content bounds of a + /// [`Scrollable`]. + /// + /// [`Scrollbar`]: struct.Scrollbar.html + /// [`Scrollable`]: struct.Scrollable.html + fn scrollbar( + &self, bounds: Rectangle, content_bounds: Rectangle, - scrollbar_bounds: Rectangle, offset: u32, - ) -> Rectangle; + ) -> Option; /// Draws the [`Scrollable`]. /// @@ -477,8 +469,7 @@ pub trait Renderer: crate::Renderer + Sized { content_bounds: Rectangle, is_mouse_over: bool, is_mouse_over_scrollbar: bool, - scrollbar_bounds: Rectangle, - scroller_bounds: Rectangle, + scrollbar: Option, offset: u32, content: Self::Output, ) -> Self::Output; diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index b83cee1b..6ef57185 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -5,30 +5,40 @@ const SCROLLBAR_WIDTH: u16 = 10; const SCROLLBAR_MARGIN: u16 = 2; impl scrollable::Renderer for Renderer { - fn scrollbar_bounds(bounds: Rectangle) -> Rectangle { - Rectangle { - x: bounds.x + bounds.width - - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - y: bounds.y, - width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - height: bounds.height, - } - } - - fn scroller_bounds( + fn scrollbar( + &self, bounds: Rectangle, content_bounds: Rectangle, - scrollbar_bounds: Rectangle, offset: u32, - ) -> Rectangle { - let ratio = bounds.height / content_bounds.height; - let scrollbar_height = bounds.height * ratio; - let y_offset = offset as f32 * ratio; - Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - y: scrollbar_bounds.y + y_offset, - width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), - height: scrollbar_height, + ) -> Option { + if content_bounds.height > bounds.height { + let scrollbar_bounds = Rectangle { + x: bounds.x + bounds.width + - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + y: bounds.y, + width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + height: bounds.height, + }; + + let ratio = bounds.height / content_bounds.height; + let scrollbar_height = bounds.height * ratio; + let y_offset = offset as f32 * ratio; + + let scroller_bounds = Rectangle { + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + y: scrollbar_bounds.y + y_offset, + width: scrollbar_bounds.width - f32::from(2 * SCROLLBAR_MARGIN), + height: scrollbar_height, + }; + + Some(scrollable::Scrollbar { + bounds: scrollbar_bounds, + scroller: scrollable::Scroller { + bounds: scroller_bounds, + }, + }) + } else { + None } } @@ -36,16 +46,13 @@ impl scrollable::Renderer for Renderer { &mut self, state: &scrollable::State, bounds: Rectangle, - content_bounds: Rectangle, + _content_bounds: Rectangle, is_mouse_over: bool, is_mouse_over_scrollbar: bool, - scrollbar_bounds: Rectangle, - scroller_bounds: Rectangle, + scrollbar: Option, offset: u32, (content, mouse_cursor): Self::Output, ) -> Self::Output { - let is_content_overflowing = content_bounds.height > bounds.height; - let clip = Primitive::Clip { bounds, offset: Vector::new(0, offset), @@ -53,36 +60,41 @@ impl scrollable::Renderer for Renderer { }; ( - if is_content_overflowing - && (is_mouse_over || state.is_scroller_grabbed()) - { - let scrollbar = Primitive::Quad { - bounds: scroller_bounds, - background: Background::Color([0.0, 0.0, 0.0, 0.7].into()), - border_radius: 5, - }; - - if is_mouse_over_scrollbar || state.is_scroller_grabbed() { - let scrollbar_background = Primitive::Quad { - bounds: Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - width: scrollbar_bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - ..scrollbar_bounds - }, + if let Some(scrollbar) = scrollbar { + if is_mouse_over || state.is_scroller_grabbed() { + let scroller = Primitive::Quad { + bounds: scrollbar.scroller.bounds, background: Background::Color( - [0.0, 0.0, 0.0, 0.3].into(), + [0.0, 0.0, 0.0, 0.7].into(), ), border_radius: 5, }; - Primitive::Group { - primitives: vec![clip, scrollbar_background, scrollbar], + if is_mouse_over_scrollbar || state.is_scroller_grabbed() { + let scrollbar = Primitive::Quad { + bounds: Rectangle { + x: scrollbar.bounds.x + + f32::from(SCROLLBAR_MARGIN), + width: scrollbar.bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar.bounds + }, + background: Background::Color( + [0.0, 0.0, 0.0, 0.3].into(), + ), + border_radius: 5, + }; + + Primitive::Group { + primitives: vec![clip, scrollbar, scroller], + } + } else { + Primitive::Group { + primitives: vec![clip, scroller], + } } } else { - Primitive::Group { - primitives: vec![clip, scrollbar], - } + clip } } else { clip -- cgit