Draw scrollbar in Widget::draw for Scrollable

This commit is contained in:
Héctor Ramón Jiménez 2021-10-18 14:48:33 +07:00
parent a4f4d83161
commit 54a9a232f8
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
11 changed files with 132 additions and 196 deletions

View file

@ -395,9 +395,7 @@ where
/// able to use a [`Menu`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer:
scrollable::Renderer + container::Renderer + text::Renderer
{
pub trait Renderer: container::Renderer + text::Renderer {
/// The [`Menu`] style supported by this renderer.
type Style: Default + Clone;
}

View file

@ -75,22 +75,6 @@ impl text::Renderer for Null {
}
}
impl scrollable::Renderer for Null {
type Style = ();
fn scrollbar(
&self,
_bounds: Rectangle,
_content_bounds: Rectangle,
_offset: u32,
_scrollbar_width: u16,
_scrollbar_margin: u16,
_scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
None
}
}
impl text_input::Renderer for Null {
type Style = ();

View file

@ -5,7 +5,6 @@ use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::overlay::menu::{self, Menu};
use crate::scrollable;
use crate::text;
use crate::touch;
use crate::{
@ -145,7 +144,7 @@ where
T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>,
Message: 'static,
Renderer: self::Renderer + scrollable::Renderer + 'a,
Renderer: self::Renderer + 'a,
{
fn width(&self) -> Length {
self.width

View file

@ -3,18 +3,21 @@ use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
use crate::renderer;
use crate::touch;
use crate::{
Alignment, Clipboard, Column, Element, Hasher, Layout, Length, Padding,
Point, Rectangle, Size, Vector, Widget,
Alignment, Background, Clipboard, Color, Column, Element, Hasher, Layout,
Length, Padding, Point, Rectangle, Size, Vector, Widget,
};
use std::{f32, hash::Hash, u32};
pub use iced_style::scrollable::StyleSheet;
/// A widget that can vertically display an infinite amount of content with a
/// scrollbar.
#[allow(missing_debug_implementations)]
pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
pub struct Scrollable<'a, Message, Renderer> {
state: &'a mut State,
height: Length,
max_height: u32,
@ -23,10 +26,10 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scroller_width: u16,
content: Column<'a, Message, Renderer>,
on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
style: Renderer::Style,
style_sheet: &'a dyn StyleSheet,
}
impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> {
/// Creates a new [`Scrollable`] with the given [`State`].
pub fn new(state: &'a mut State) -> Self {
Scrollable {
@ -38,7 +41,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scroller_width: 10,
content: Column::new(),
on_scroll: None,
style: Renderer::Style::default(),
style_sheet: Default::default(),
}
}
@ -119,8 +122,11 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
pub fn style<'b>(mut self, style_sheet: &'b dyn StyleSheet) -> Self
where
'b: 'a,
{
self.style_sheet = style_sheet;
self
}
@ -150,12 +156,63 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
));
}
}
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
) -> Option<Scrollbar> {
let offset = self.state.offset(bounds, content_bounds);
if content_bounds.height > bounds.height {
let outer_width = self.scrollbar_width.max(self.scroller_width)
+ 2 * self.scrollbar_margin;
let outer_bounds = Rectangle {
x: bounds.x + bounds.width - outer_width as f32,
y: bounds.y,
width: outer_width as f32,
height: bounds.height,
};
let scrollbar_bounds = Rectangle {
x: bounds.x + bounds.width
- f32::from(outer_width / 2 + self.scrollbar_width / 2),
y: bounds.y,
width: self.scrollbar_width as f32,
height: bounds.height,
};
let ratio = bounds.height / content_bounds.height;
let scroller_height = bounds.height * ratio;
let y_offset = offset as f32 * ratio;
let scroller_bounds = Rectangle {
x: bounds.x + bounds.width
- f32::from(outer_width / 2 + self.scroller_width / 2),
y: scrollbar_bounds.y + y_offset,
width: self.scroller_width as f32,
height: scroller_height,
};
Some(Scrollbar {
outer_bounds,
bounds: scrollbar_bounds,
margin: self.scrollbar_margin,
scroller: Scroller {
bounds: scroller_bounds,
},
})
} else {
None
}
}
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for Scrollable<'a, Message, Renderer>
where
Renderer: self::Renderer,
Renderer: crate::Renderer,
{
fn width(&self) -> Length {
Widget::<Message, Renderer>::width(&self.content)
@ -201,15 +258,7 @@ where
let content = layout.children().next().unwrap();
let content_bounds = content.bounds();
let offset = self.state.offset(bounds, content_bounds);
let scrollbar = renderer.scrollbar(
bounds,
content_bounds,
offset,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
);
let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over_scrollbar = scrollbar
.as_ref()
.map(|scrollbar| scrollbar.is_mouse_over(cursor_position))
@ -385,14 +434,7 @@ 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,
self.scrollbar_width,
self.scrollbar_margin,
self.scroller_width,
);
let scrollbar = self.scrollbar(bounds, content_bounds);
let is_mouse_over = bounds.contains(cursor_position);
let is_mouse_over_scrollbar = scrollbar
@ -420,43 +462,43 @@ where
);
});
// TODO: Draw scroller
// let style = if self.state.is_scroller_grabbed() {
// style_sheet.dragging()
// } else if is_mouse_over_scrollbar {
// style_sheet.hovered()
// } else {
// style_sheet.active()
// };
let style = if self.state.is_scroller_grabbed() {
self.style_sheet.dragging()
} else if is_mouse_over_scrollbar {
self.style_sheet.hovered()
} else {
self.style_sheet.active()
};
// let is_scrollbar_visible =
// style.background.is_some() || style.border_width > 0.0;
let is_scrollbar_visible =
style.background.is_some() || style.border_width > 0.0;
// if is_mouse_over
// || self.state.is_scroller_grabbed()
// || is_scrollbar_visible
// {
// // Primitive::Quad {
// // bounds: scrollbar.scroller.bounds,
// // background: Background::Color(style.scroller.color),
// // border_radius: style.scroller.border_radius,
// // border_width: style.scroller.border_width,
// // border_color: style.scroller.border_color,
// // }
// };
renderer.with_layer(bounds, Vector::new(0, 0), |renderer| {
if is_scrollbar_visible {
renderer.fill_rectangle(renderer::Quad {
bounds: scrollbar.bounds,
background: style
.background
.unwrap_or(Background::Color(Color::TRANSPARENT)),
border_radius: style.border_radius,
border_width: style.border_width,
border_color: style.border_color,
});
}
// TODO: Draw scrollbar
// if is_scrollbar_visible {
// Primitive::Quad {
// bounds: scrollbar.bounds,
// background: style
// .background
// .unwrap_or(Background::Color(Color::TRANSPARENT)),
// border_radius: style.border_radius,
// border_width: style.border_width,
// border_color: style.border_color,
// }
//}
if is_mouse_over
|| self.state.is_scroller_grabbed()
|| is_scrollbar_visible
{
renderer.fill_rectangle(renderer::Quad {
bounds: scrollbar.scroller.bounds,
background: Background::Color(style.scroller.color),
border_radius: style.scroller.border_radius,
border_width: style.scroller.border_width,
border_color: style.scroller.border_color,
});
}
});
} else {
self.content.draw(
renderer,
@ -614,19 +656,19 @@ impl State {
/// The scrollbar of a [`Scrollable`].
#[derive(Debug)]
pub struct Scrollbar {
struct Scrollbar {
/// The outer bounds of the scrollable, including the [`Scrollbar`] and
/// [`Scroller`].
pub outer_bounds: Rectangle,
outer_bounds: Rectangle,
/// The bounds of the [`Scrollbar`].
pub bounds: Rectangle,
bounds: Rectangle,
/// The margin within the [`Scrollbar`].
pub margin: u16,
margin: u16,
/// The bounds of the [`Scroller`].
pub scroller: Scroller,
scroller: Scroller,
}
impl Scrollbar {
@ -661,38 +703,15 @@ impl Scrollbar {
/// The handle of a [`Scrollbar`].
#[derive(Debug, Clone, Copy)]
pub struct Scroller {
struct Scroller {
/// The bounds of the [`Scroller`].
pub bounds: Rectangle,
}
/// The renderer of a [`Scrollable`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`Scrollable`] in your user interface.
///
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// The style supported by this renderer.
type Style: Default;
/// Returns the [`Scrollbar`] given the bounds and content bounds of a
/// [`Scrollable`].
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
) -> Option<Scrollbar>;
bounds: Rectangle,
}
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: 'a + self::Renderer,
Renderer: 'a + crate::Renderer,
Message: 'a,
{
fn from(