summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/widget/scrollable.rs20
-rw-r--r--native/src/widget/scrollable.rs65
-rw-r--r--wgpu/src/renderer/scrollable.rs87
3 files changed, 155 insertions, 17 deletions
diff --git a/core/src/widget/scrollable.rs b/core/src/widget/scrollable.rs
index 1bad8555..31a5abed 100644
--- a/core/src/widget/scrollable.rs
+++ b/core/src/widget/scrollable.rs
@@ -1,4 +1,4 @@
-use crate::{Align, Column, Length, Rectangle};
+use crate::{Align, Column, Length, Point, Rectangle};
#[derive(Debug)]
pub struct Scrollable<'a, Element> {
@@ -103,6 +103,7 @@ impl<'a, Element> Scrollable<'a, Element> {
#[derive(Debug, Clone, Copy, Default)]
pub struct State {
+ pub scrollbar_grabbed_at: Option<Point>,
offset: u32,
}
@@ -121,17 +122,30 @@ impl State {
return;
}
- // TODO: Configurable speed (?)
- self.offset = (self.offset as i32 - delta_y.round() as i32 * 15)
+ self.offset = (self.offset as i32 - delta_y.round() as i32)
.max(0)
.min((content_bounds.height - bounds.height) as i32)
as u32;
}
+ pub fn scroll_to(
+ &mut self,
+ percentage: f32,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ ) {
+ self.offset = ((content_bounds.height - bounds.height) * percentage)
+ .max(0.0) as u32;
+ }
+
pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
let hidden_content =
(content_bounds.height - bounds.height).round() as u32;
self.offset.min(hidden_content).max(0)
}
+
+ pub fn is_scrollbar_grabbed(&self) -> bool {
+ self.scrollbar_grabbed_at.is_some()
+ }
}
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<Point>` 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<Message>(
&mut self,
scrollable: &Scrollable<'_, Message, Self>,
diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs
index 1327e577..7f8a7db6 100644
--- a/wgpu/src/renderer/scrollable.rs
+++ b/wgpu/src/renderer/scrollable.rs
@@ -1,9 +1,33 @@
use crate::{Primitive, Renderer};
use iced_native::{
- scrollable, Background, Color, Layout, Point, Rectangle, Scrollable, Widget,
+ scrollable, Background, Color, Layout, MouseCursor, Point, Rectangle,
+ Scrollable, Widget,
};
+const SCROLLBAR_WIDTH: u16 = 10;
+const SCROLLBAR_MARGIN: u16 = 10;
+
+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,
+ }
+}
+
impl scrollable::Renderer for Renderer {
+ fn is_mouse_over_scrollbar(
+ &self,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ cursor_position: Point,
+ ) -> bool {
+ content_bounds.height > bounds.height
+ && scrollbar_bounds(bounds).contains(cursor_position)
+ }
+
fn draw<Message>(
&mut self,
scrollable: &Scrollable<'_, Message, Self>,
@@ -15,8 +39,15 @@ impl scrollable::Renderer for Renderer {
let content_bounds = content.bounds();
let offset = scrollable.state.offset(bounds, content_bounds);
+ let is_content_overflowing = content_bounds.height > bounds.height;
+ let scrollbar_bounds = scrollbar_bounds(bounds);
+ let is_mouse_over_scrollbar = self.is_mouse_over_scrollbar(
+ bounds,
+ content_bounds,
+ cursor_position,
+ );
- let cursor_position = if bounds.contains(cursor_position) {
+ let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {
Point::new(cursor_position.x, cursor_position.y + offset as f32)
} else {
Point::new(cursor_position.x, -1.0)
@@ -32,16 +63,19 @@ impl scrollable::Renderer for Renderer {
};
(
- if is_mouse_over && content_bounds.height > bounds.height {
+ if is_content_overflowing
+ && (is_mouse_over || scrollable.state.is_scrollbar_grabbed())
+ {
let ratio = bounds.height / content_bounds.height;
let scrollbar_height = bounds.height * ratio;
let y_offset = offset as f32 * ratio;
let scrollbar = Primitive::Quad {
bounds: Rectangle {
- x: bounds.x + bounds.width - 12.0,
- y: bounds.y + y_offset,
- width: 10.0,
+ 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,
},
background: Background::Color(Color {
@@ -52,13 +86,48 @@ impl scrollable::Renderer for Renderer {
}),
border_radius: 5,
};
- Primitive::Group {
- primitives: vec![primitive, scrollbar],
+
+ if is_mouse_over_scrollbar
+ || scrollable.state.is_scrollbar_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
+ },
+ background: Background::Color(Color {
+ r: 0.0,
+ g: 0.0,
+ b: 0.0,
+ a: 0.3,
+ }),
+ border_radius: 5,
+ };
+
+ Primitive::Group {
+ primitives: vec![
+ primitive,
+ scrollbar_background,
+ scrollbar,
+ ],
+ }
+ } else {
+ Primitive::Group {
+ primitives: vec![primitive, scrollbar],
+ }
}
} else {
primitive
},
- mouse_cursor,
+ if is_mouse_over_scrollbar
+ || scrollable.state.is_scrollbar_grabbed()
+ {
+ MouseCursor::Idle
+ } else {
+ mouse_cursor
+ },
)
}
}