Draw scrollbar in Widget::draw for Scrollable
This commit is contained in:
parent
a4f4d83161
commit
54a9a232f8
11 changed files with 132 additions and 196 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,3 +23,7 @@ path = "../core"
|
|||
version = "0.3"
|
||||
path = "../futures"
|
||||
features = ["thread-pool"]
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.3"
|
||||
path = "../style"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = ();
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue