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

@ -95,7 +95,7 @@ impl Sandbox for ScrollableDemo {
.on_scroll(move |offset| {
Message::Scrolled(i, offset)
})
.style(*theme)
.style(theme.clone().into())
.push(Text::new(variant.title))
.push(
Button::new(

View file

@ -34,11 +34,11 @@ impl From<Theme> for Box<dyn radio::StyleSheet> {
}
}
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
impl From<Theme> for &'static dyn scrollable::StyleSheet {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
Theme::Dark => dark::Scrollable.into(),
Theme::Dark => &dark::Scrollable,
}
}
}

View file

@ -98,7 +98,7 @@ impl Sandbox for Styling {
let scrollable = Scrollable::new(&mut self.scroll)
.width(Length::Fill)
.height(Length::Units(100))
.style(self.theme)
.style(self.theme.into())
.push(Text::new("Scroll me!"))
.push(Space::with_height(Length::Units(800)))
.push(Text::new("You did it!"));
@ -212,11 +212,11 @@ mod style {
}
}
impl From<Theme> for Box<dyn scrollable::StyleSheet> {
impl From<Theme> for &'static dyn scrollable::StyleSheet {
fn from(theme: Theme) -> Self {
match theme {
Theme::Light => Default::default(),
Theme::Dark => dark::Scrollable.into(),
Theme::Dark => &dark::Scrollable,
}
}
}

View file

@ -1,7 +1,5 @@
//! Navigate an endless amount of content with a scrollbar.
use crate::{Backend, Renderer};
use iced_native::scrollable;
use iced_native::Rectangle;
use crate::Renderer;
pub use iced_native::scrollable::State;
pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
@ -13,63 +11,3 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
/// `Renderer`.
pub type Scrollable<'a, Message, Backend> =
iced_native::Scrollable<'a, Message, Renderer<Backend>>;
impl<B> scrollable::Renderer for Renderer<B>
where
B: Backend,
{
type Style = Box<dyn iced_style::scrollable::StyleSheet>;
fn scrollbar(
&self,
bounds: Rectangle,
content_bounds: Rectangle,
offset: u32,
scrollbar_width: u16,
scrollbar_margin: u16,
scroller_width: u16,
) -> Option<scrollable::Scrollbar> {
if content_bounds.height > bounds.height {
let outer_width =
scrollbar_width.max(scroller_width) + 2 * 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 + scrollbar_width / 2),
y: bounds.y,
width: 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 + scroller_width / 2),
y: scrollbar_bounds.y + y_offset,
width: scroller_width as f32,
height: scroller_height,
};
Some(scrollable::Scrollbar {
outer_bounds,
bounds: scrollbar_bounds,
margin: scrollbar_margin,
scroller: scrollable::Scroller {
bounds: scroller_bounds,
},
})
} else {
None
}
}
}

View file

@ -23,3 +23,7 @@ path = "../core"
version = "0.3"
path = "../futures"
features = ["thread-pool"]
[dependencies.iced_style]
version = "0.3"
path = "../style"

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(

View file

@ -60,17 +60,8 @@ impl StyleSheet for Default {
}
}
impl std::default::Default for Box<dyn StyleSheet> {
impl std::default::Default for &'static dyn StyleSheet {
fn default() -> Self {
Box::new(Default)
}
}
impl<T> From<T> for Box<dyn StyleSheet>
where
T: 'static + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
&Default
}
}

View file

@ -14,7 +14,7 @@ pub struct Scrollable<'a, Message> {
max_height: u32,
content: Column<'a, Message>,
#[allow(dead_code)]
style: Box<dyn StyleSheet>,
style_sheet: &'a dyn StyleSheet,
}
impl<'a, Message> Scrollable<'a, Message> {
@ -27,7 +27,7 @@ impl<'a, Message> Scrollable<'a, Message> {
height: Length::Shrink,
max_height: u32::MAX,
content: Column::new(),
style: Default::default(),
style_sheet: Default::default(),
}
}
@ -78,8 +78,11 @@ impl<'a, Message> Scrollable<'a, Message> {
}
/// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Box<dyn StyleSheet>>) -> 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
}