Add support for embedded scrollbars for scrollable
Co-authored-by: dtzxporter <dtzxporter@users.noreply.github.com>
This commit is contained in:
parent
3c55e07668
commit
8ae4e09db9
4 changed files with 325 additions and 212 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::Size;
|
use crate::{Pixels, Size};
|
||||||
|
|
||||||
/// An amount of space to pad for each side of a box
|
/// An amount of space to pad for each side of a box
|
||||||
///
|
///
|
||||||
|
|
@ -54,7 +54,7 @@ impl Padding {
|
||||||
left: 0.0,
|
left: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Create a Padding that is equal on all sides
|
/// Create a [`Padding`] that is equal on all sides.
|
||||||
pub const fn new(padding: f32) -> Padding {
|
pub const fn new(padding: f32) -> Padding {
|
||||||
Padding {
|
Padding {
|
||||||
top: padding,
|
top: padding,
|
||||||
|
|
@ -64,6 +64,38 @@ impl Padding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create some top [`Padding`].
|
||||||
|
pub fn top(padding: impl Into<Pixels>) -> Self {
|
||||||
|
Self {
|
||||||
|
top: padding.into().0,
|
||||||
|
..Self::ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create some right [`Padding`].
|
||||||
|
pub fn right(padding: impl Into<Pixels>) -> Self {
|
||||||
|
Self {
|
||||||
|
right: padding.into().0,
|
||||||
|
..Self::ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create some bottom [`Padding`].
|
||||||
|
pub fn bottom(padding: impl Into<Pixels>) -> Self {
|
||||||
|
Self {
|
||||||
|
bottom: padding.into().0,
|
||||||
|
..Self::ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create some left [`Padding`].
|
||||||
|
pub fn left(padding: impl Into<Pixels>) -> Self {
|
||||||
|
Self {
|
||||||
|
left: padding.into().0,
|
||||||
|
..Self::ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the total amount of vertical [`Padding`].
|
/// Returns the total amount of vertical [`Padding`].
|
||||||
pub fn vertical(self) -> f32 {
|
pub fn vertical(self) -> f32 {
|
||||||
self.top + self.bottom
|
self.top + self.bottom
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use iced::widget::scrollable::Properties;
|
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, column, container, horizontal_space, progress_bar, radio, row,
|
button, column, container, horizontal_space, progress_bar, radio, row,
|
||||||
scrollable, slider, text, vertical_space, Scrollable,
|
scrollable, slider, text, vertical_space,
|
||||||
};
|
};
|
||||||
use iced::{Alignment, Border, Color, Element, Length, Task, Theme};
|
use iced::{Alignment, Border, Color, Element, Length, Task, Theme};
|
||||||
|
|
||||||
|
|
@ -203,7 +202,7 @@ impl ScrollableDemo {
|
||||||
|
|
||||||
let scrollable_content: Element<Message> =
|
let scrollable_content: Element<Message> =
|
||||||
Element::from(match self.scrollable_direction {
|
Element::from(match self.scrollable_direction {
|
||||||
Direction::Vertical => Scrollable::with_direction(
|
Direction::Vertical => scrollable(
|
||||||
column![
|
column![
|
||||||
scroll_to_end_button(),
|
scroll_to_end_button(),
|
||||||
text("Beginning!"),
|
text("Beginning!"),
|
||||||
|
|
@ -216,19 +215,19 @@ impl ScrollableDemo {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.padding([40, 0, 40, 0])
|
.padding([40, 0, 40, 0])
|
||||||
.spacing(40),
|
.spacing(40),
|
||||||
scrollable::Direction::Vertical(
|
)
|
||||||
Properties::new()
|
.direction(scrollable::Direction::Vertical(
|
||||||
|
scrollable::Scrollbar::new()
|
||||||
.width(self.scrollbar_width)
|
.width(self.scrollbar_width)
|
||||||
.margin(self.scrollbar_margin)
|
.margin(self.scrollbar_margin)
|
||||||
.scroller_width(self.scroller_width)
|
.scroller_width(self.scroller_width)
|
||||||
.alignment(self.alignment),
|
.alignment(self.alignment),
|
||||||
),
|
))
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.id(SCROLLABLE_ID.clone())
|
.id(SCROLLABLE_ID.clone())
|
||||||
.on_scroll(Message::Scrolled),
|
.on_scroll(Message::Scrolled),
|
||||||
Direction::Horizontal => Scrollable::with_direction(
|
Direction::Horizontal => scrollable(
|
||||||
row![
|
row![
|
||||||
scroll_to_end_button(),
|
scroll_to_end_button(),
|
||||||
text("Beginning!"),
|
text("Beginning!"),
|
||||||
|
|
@ -242,19 +241,19 @@ impl ScrollableDemo {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.padding([0, 40, 0, 40])
|
.padding([0, 40, 0, 40])
|
||||||
.spacing(40),
|
.spacing(40),
|
||||||
scrollable::Direction::Horizontal(
|
)
|
||||||
Properties::new()
|
.direction(scrollable::Direction::Horizontal(
|
||||||
|
scrollable::Scrollbar::new()
|
||||||
.width(self.scrollbar_width)
|
.width(self.scrollbar_width)
|
||||||
.margin(self.scrollbar_margin)
|
.margin(self.scrollbar_margin)
|
||||||
.scroller_width(self.scroller_width)
|
.scroller_width(self.scroller_width)
|
||||||
.alignment(self.alignment),
|
.alignment(self.alignment),
|
||||||
),
|
))
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.id(SCROLLABLE_ID.clone())
|
.id(SCROLLABLE_ID.clone())
|
||||||
.on_scroll(Message::Scrolled),
|
.on_scroll(Message::Scrolled),
|
||||||
Direction::Multi => Scrollable::with_direction(
|
Direction::Multi => scrollable(
|
||||||
//horizontal content
|
//horizontal content
|
||||||
row![
|
row![
|
||||||
column![
|
column![
|
||||||
|
|
@ -284,19 +283,19 @@ impl ScrollableDemo {
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.padding([0, 40, 0, 40])
|
.padding([0, 40, 0, 40])
|
||||||
.spacing(40),
|
.spacing(40),
|
||||||
{
|
)
|
||||||
let properties = Properties::new()
|
.direction({
|
||||||
|
let scrollbar = scrollable::Scrollbar::new()
|
||||||
.width(self.scrollbar_width)
|
.width(self.scrollbar_width)
|
||||||
.margin(self.scrollbar_margin)
|
.margin(self.scrollbar_margin)
|
||||||
.scroller_width(self.scroller_width)
|
.scroller_width(self.scroller_width)
|
||||||
.alignment(self.alignment);
|
.alignment(self.alignment);
|
||||||
|
|
||||||
scrollable::Direction::Both {
|
scrollable::Direction::Both {
|
||||||
horizontal: properties,
|
horizontal: scrollbar,
|
||||||
vertical: properties,
|
vertical: scrollbar,
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.id(SCROLLABLE_ID.clone())
|
.id(SCROLLABLE_ID.clone())
|
||||||
|
|
|
||||||
|
|
@ -200,8 +200,7 @@ where
|
||||||
class,
|
class,
|
||||||
} = menu;
|
} = menu;
|
||||||
|
|
||||||
let list = Scrollable::with_direction(
|
let list = Scrollable::new(List {
|
||||||
List {
|
|
||||||
options,
|
options,
|
||||||
hovered_option,
|
hovered_option,
|
||||||
on_selected,
|
on_selected,
|
||||||
|
|
@ -212,9 +211,7 @@ where
|
||||||
text_shaping,
|
text_shaping,
|
||||||
padding,
|
padding,
|
||||||
class,
|
class,
|
||||||
},
|
});
|
||||||
scrollable::Direction::default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
state.tree.diff(&list as &dyn Widget<_, _, _>);
|
state.tree.diff(&list as &dyn Widget<_, _, _>);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use crate::core::widget::operation::{self, Operation};
|
||||||
use crate::core::widget::tree::{self, Tree};
|
use crate::core::widget::tree::{self, Tree};
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Background, Border, Clipboard, Color, Element, Layout, Length,
|
self, Background, Border, Clipboard, Color, Element, Layout, Length,
|
||||||
Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::runtime::task::{self, Task};
|
use crate::runtime::task::{self, Task};
|
||||||
use crate::runtime::Action;
|
use crate::runtime::Action;
|
||||||
|
|
@ -49,37 +49,38 @@ where
|
||||||
pub fn new(
|
pub fn new(
|
||||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::with_direction(content, Direction::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new [`Scrollable`] with the given [`Direction`].
|
|
||||||
pub fn with_direction(
|
|
||||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
|
||||||
direction: Direction,
|
|
||||||
) -> Self {
|
|
||||||
let content = content.into();
|
|
||||||
|
|
||||||
debug_assert!(
|
|
||||||
direction.vertical().is_none()
|
|
||||||
|| !content.as_widget().size_hint().height.is_fill(),
|
|
||||||
"scrollable content must not fill its vertical scrolling axis"
|
|
||||||
);
|
|
||||||
|
|
||||||
debug_assert!(
|
|
||||||
direction.horizontal().is_none()
|
|
||||||
|| !content.as_widget().size_hint().width.is_fill(),
|
|
||||||
"scrollable content must not fill its horizontal scrolling axis"
|
|
||||||
);
|
|
||||||
|
|
||||||
Scrollable {
|
Scrollable {
|
||||||
id: None,
|
id: None,
|
||||||
width: Length::Shrink,
|
width: Length::Shrink,
|
||||||
height: Length::Shrink,
|
height: Length::Shrink,
|
||||||
direction,
|
direction: Direction::default(),
|
||||||
content,
|
content: content.into(),
|
||||||
on_scroll: None,
|
on_scroll: None,
|
||||||
class: Theme::default(),
|
class: Theme::default(),
|
||||||
}
|
}
|
||||||
|
.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(self) -> Self {
|
||||||
|
debug_assert!(
|
||||||
|
self.direction.vertical().is_none()
|
||||||
|
|| !self.content.as_widget().size_hint().height.is_fill(),
|
||||||
|
"scrollable content must not fill its vertical scrolling axis"
|
||||||
|
);
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
self.direction.horizontal().is_none()
|
||||||
|
|| !self.content.as_widget().size_hint().width.is_fill(),
|
||||||
|
"scrollable content must not fill its horizontal scrolling axis"
|
||||||
|
);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`Scrollable`] with the given [`Direction`].
|
||||||
|
pub fn direction(mut self, direction: impl Into<Direction>) -> Self {
|
||||||
|
self.direction = direction.into();
|
||||||
|
self.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Id`] of the [`Scrollable`].
|
/// Sets the [`Id`] of the [`Scrollable`].
|
||||||
|
|
@ -108,7 +109,7 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inverts the alignment of the horizontal direction of the [`Scrollable`], if applicable.
|
/// Sets the alignment of the horizontal direction of the [`Scrollable`], if applicable.
|
||||||
pub fn align_x(mut self, alignment: Alignment) -> Self {
|
pub fn align_x(mut self, alignment: Alignment) -> Self {
|
||||||
match &mut self.direction {
|
match &mut self.direction {
|
||||||
Direction::Horizontal(horizontal)
|
Direction::Horizontal(horizontal)
|
||||||
|
|
@ -134,6 +135,32 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether the horizontal [`Scrollbar`] should be embedded in the [`Scrollable`].
|
||||||
|
pub fn embed_x(mut self, embedded: bool) -> Self {
|
||||||
|
match &mut self.direction {
|
||||||
|
Direction::Horizontal(horizontal)
|
||||||
|
| Direction::Both { horizontal, .. } => {
|
||||||
|
horizontal.embedded = embedded;
|
||||||
|
}
|
||||||
|
Direction::Vertical(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether the vertical [`Scrollbar`] should be embedded in the [`Scrollable`].
|
||||||
|
pub fn embed_y(mut self, embedded: bool) -> Self {
|
||||||
|
match &mut self.direction {
|
||||||
|
Direction::Vertical(vertical)
|
||||||
|
| Direction::Both { vertical, .. } => {
|
||||||
|
vertical.embedded = embedded;
|
||||||
|
}
|
||||||
|
Direction::Horizontal(_) => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the style of this [`Scrollable`].
|
/// Sets the style of this [`Scrollable`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
|
pub fn style(mut self, style: impl Fn(&Theme, Status) -> Style + 'a) -> Self
|
||||||
|
|
@ -157,21 +184,21 @@ where
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
/// Vertical scrolling
|
/// Vertical scrolling
|
||||||
Vertical(Properties),
|
Vertical(Scrollbar),
|
||||||
/// Horizontal scrolling
|
/// Horizontal scrolling
|
||||||
Horizontal(Properties),
|
Horizontal(Scrollbar),
|
||||||
/// Both vertical and horizontal scrolling
|
/// Both vertical and horizontal scrolling
|
||||||
Both {
|
Both {
|
||||||
/// The properties of the vertical scrollbar.
|
/// The properties of the vertical scrollbar.
|
||||||
vertical: Properties,
|
vertical: Scrollbar,
|
||||||
/// The properties of the horizontal scrollbar.
|
/// The properties of the horizontal scrollbar.
|
||||||
horizontal: Properties,
|
horizontal: Scrollbar,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Direction {
|
impl Direction {
|
||||||
/// Returns the [`Properties`] of the horizontal scrollbar, if any.
|
/// Returns the [`Properties`] of the horizontal scrollbar, if any.
|
||||||
pub fn horizontal(&self) -> Option<&Properties> {
|
pub fn horizontal(&self) -> Option<&Scrollbar> {
|
||||||
match self {
|
match self {
|
||||||
Self::Horizontal(properties) => Some(properties),
|
Self::Horizontal(properties) => Some(properties),
|
||||||
Self::Both { horizontal, .. } => Some(horizontal),
|
Self::Both { horizontal, .. } => Some(horizontal),
|
||||||
|
|
@ -180,7 +207,7 @@ impl Direction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`Properties`] of the vertical scrollbar, if any.
|
/// Returns the [`Properties`] of the vertical scrollbar, if any.
|
||||||
pub fn vertical(&self) -> Option<&Properties> {
|
pub fn vertical(&self) -> Option<&Scrollbar> {
|
||||||
match self {
|
match self {
|
||||||
Self::Vertical(properties) => Some(properties),
|
Self::Vertical(properties) => Some(properties),
|
||||||
Self::Both { vertical, .. } => Some(vertical),
|
Self::Both { vertical, .. } => Some(vertical),
|
||||||
|
|
@ -191,31 +218,33 @@ impl Direction {
|
||||||
|
|
||||||
impl Default for Direction {
|
impl Default for Direction {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Vertical(Properties::default())
|
Self::Vertical(Scrollbar::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Properties of a scrollbar within a [`Scrollable`].
|
/// Properties of a scrollbar within a [`Scrollable`].
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Properties {
|
pub struct Scrollbar {
|
||||||
width: f32,
|
width: f32,
|
||||||
margin: f32,
|
margin: f32,
|
||||||
scroller_width: f32,
|
scroller_width: f32,
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
|
embedded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Properties {
|
impl Default for Scrollbar {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
width: 10.0,
|
width: 10.0,
|
||||||
margin: 0.0,
|
margin: 0.0,
|
||||||
scroller_width: 10.0,
|
scroller_width: 10.0,
|
||||||
alignment: Alignment::Start,
|
alignment: Alignment::Start,
|
||||||
|
embedded: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Properties {
|
impl Scrollbar {
|
||||||
/// Creates new [`Properties`] for use in a [`Scrollable`].
|
/// Creates new [`Properties`] for use in a [`Scrollable`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
|
|
@ -244,6 +273,15 @@ impl Properties {
|
||||||
self.alignment = alignment;
|
self.alignment = alignment;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets whether the [`Scrollbar`] should be embedded in the [`Scrollable`].
|
||||||
|
///
|
||||||
|
/// An embedded [`Scrollbar`] will always be displayed, will take layout space,
|
||||||
|
/// and will not float over the contents.
|
||||||
|
pub fn embedded(mut self, embedded: bool) -> Self {
|
||||||
|
self.embedded = embedded;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Alignment of the scrollable's content relative to it's [`Viewport`] in one direction.
|
/// Alignment of the scrollable's content relative to it's [`Viewport`] in one direction.
|
||||||
|
|
@ -291,7 +329,26 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
layout::contained(limits, self.width, self.height, |limits| {
|
let (right_padding, bottom_padding) = match self.direction {
|
||||||
|
Direction::Vertical(scrollbar) if scrollbar.embedded => {
|
||||||
|
(scrollbar.width + scrollbar.margin * 2.0, 0.0)
|
||||||
|
}
|
||||||
|
Direction::Horizontal(scrollbar) if scrollbar.embedded => {
|
||||||
|
(0.0, scrollbar.width + scrollbar.margin * 2.0)
|
||||||
|
}
|
||||||
|
_ => (0.0, 0.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
layout::padded(
|
||||||
|
limits,
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
Padding {
|
||||||
|
right: right_padding,
|
||||||
|
bottom: bottom_padding,
|
||||||
|
..Padding::ZERO
|
||||||
|
},
|
||||||
|
|limits| {
|
||||||
let child_limits = layout::Limits::new(
|
let child_limits = layout::Limits::new(
|
||||||
Size::new(limits.min().width, limits.min().height),
|
Size::new(limits.min().width, limits.min().height),
|
||||||
Size::new(
|
Size::new(
|
||||||
|
|
@ -313,7 +370,8 @@ where
|
||||||
renderer,
|
renderer,
|
||||||
&child_limits,
|
&child_limits,
|
||||||
)
|
)
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
|
|
@ -762,7 +820,7 @@ where
|
||||||
|
|
||||||
let draw_scrollbar =
|
let draw_scrollbar =
|
||||||
|renderer: &mut Renderer,
|
|renderer: &mut Renderer,
|
||||||
style: Scrollbar,
|
style: Rail,
|
||||||
scrollbar: &internals::Scrollbar| {
|
scrollbar: &internals::Scrollbar| {
|
||||||
if scrollbar.bounds.width > 0.0
|
if scrollbar.bounds.width > 0.0
|
||||||
&& scrollbar.bounds.height > 0.0
|
&& scrollbar.bounds.height > 0.0
|
||||||
|
|
@ -782,8 +840,9 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if scrollbar.scroller.bounds.width > 0.0
|
if let Some(scroller) = scrollbar.scroller {
|
||||||
&& scrollbar.scroller.bounds.height > 0.0
|
if scroller.bounds.width > 0.0
|
||||||
|
&& scroller.bounds.height > 0.0
|
||||||
&& (style.scroller.color != Color::TRANSPARENT
|
&& (style.scroller.color != Color::TRANSPARENT
|
||||||
|| (style.scroller.border.color
|
|| (style.scroller.border.color
|
||||||
!= Color::TRANSPARENT
|
!= Color::TRANSPARENT
|
||||||
|
|
@ -791,13 +850,14 @@ where
|
||||||
{
|
{
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: scrollbar.scroller.bounds,
|
bounds: scroller.bounds,
|
||||||
border: style.scroller.border,
|
border: style.scroller.border,
|
||||||
..renderer::Quad::default()
|
..renderer::Quad::default()
|
||||||
},
|
},
|
||||||
style.scroller.color,
|
style.scroller.color,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.with_layer(
|
renderer.with_layer(
|
||||||
|
|
@ -810,7 +870,7 @@ where
|
||||||
if let Some(scrollbar) = scrollbars.y {
|
if let Some(scrollbar) = scrollbars.y {
|
||||||
draw_scrollbar(
|
draw_scrollbar(
|
||||||
renderer,
|
renderer,
|
||||||
style.vertical_scrollbar,
|
style.vertical_rail,
|
||||||
&scrollbar,
|
&scrollbar,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -818,7 +878,7 @@ where
|
||||||
if let Some(scrollbar) = scrollbars.x {
|
if let Some(scrollbar) = scrollbars.x {
|
||||||
draw_scrollbar(
|
draw_scrollbar(
|
||||||
renderer,
|
renderer,
|
||||||
style.horizontal_scrollbar,
|
style.horizontal_rail,
|
||||||
&scrollbar,
|
&scrollbar,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1324,16 +1384,16 @@ impl Scrollbars {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let translation = state.translation(direction, bounds, content_bounds);
|
let translation = state.translation(direction, bounds, content_bounds);
|
||||||
|
|
||||||
let show_scrollbar_x = direction
|
let show_scrollbar_x = direction.horizontal().filter(|scrollbar| {
|
||||||
.horizontal()
|
scrollbar.embedded || content_bounds.width > bounds.width
|
||||||
.filter(|_| content_bounds.width > bounds.width);
|
});
|
||||||
|
|
||||||
let show_scrollbar_y = direction
|
let show_scrollbar_y = direction.vertical().filter(|scrollbar| {
|
||||||
.vertical()
|
scrollbar.embedded || content_bounds.height > bounds.height
|
||||||
.filter(|_| content_bounds.height > bounds.height);
|
});
|
||||||
|
|
||||||
let y_scrollbar = if let Some(vertical) = show_scrollbar_y {
|
let y_scrollbar = if let Some(vertical) = show_scrollbar_y {
|
||||||
let Properties {
|
let Scrollbar {
|
||||||
width,
|
width,
|
||||||
margin,
|
margin,
|
||||||
scroller_width,
|
scroller_width,
|
||||||
|
|
@ -1367,10 +1427,16 @@ impl Scrollbars {
|
||||||
};
|
};
|
||||||
|
|
||||||
let ratio = bounds.height / content_bounds.height;
|
let ratio = bounds.height / content_bounds.height;
|
||||||
|
|
||||||
|
let scroller = if ratio >= 1.0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
// min height for easier grabbing with super tall content
|
// min height for easier grabbing with super tall content
|
||||||
let scroller_height = (scrollbar_bounds.height * ratio).max(2.0);
|
let scroller_height =
|
||||||
|
(scrollbar_bounds.height * ratio).max(2.0);
|
||||||
let scroller_offset =
|
let scroller_offset =
|
||||||
translation.y * ratio * scrollbar_bounds.height / bounds.height;
|
translation.y * ratio * scrollbar_bounds.height
|
||||||
|
/ bounds.height;
|
||||||
|
|
||||||
let scroller_bounds = Rectangle {
|
let scroller_bounds = Rectangle {
|
||||||
x: bounds.x + bounds.width
|
x: bounds.x + bounds.width
|
||||||
|
|
@ -1381,12 +1447,15 @@ impl Scrollbars {
|
||||||
height: scroller_height,
|
height: scroller_height,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Some(internals::Scroller {
|
||||||
|
bounds: scroller_bounds,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
Some(internals::Scrollbar {
|
Some(internals::Scrollbar {
|
||||||
total_bounds: total_scrollbar_bounds,
|
total_bounds: total_scrollbar_bounds,
|
||||||
bounds: scrollbar_bounds,
|
bounds: scrollbar_bounds,
|
||||||
scroller: internals::Scroller {
|
scroller,
|
||||||
bounds: scroller_bounds,
|
|
||||||
},
|
|
||||||
alignment: vertical.alignment,
|
alignment: vertical.alignment,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1394,7 +1463,7 @@ impl Scrollbars {
|
||||||
};
|
};
|
||||||
|
|
||||||
let x_scrollbar = if let Some(horizontal) = show_scrollbar_x {
|
let x_scrollbar = if let Some(horizontal) = show_scrollbar_x {
|
||||||
let Properties {
|
let Scrollbar {
|
||||||
width,
|
width,
|
||||||
margin,
|
margin,
|
||||||
scroller_width,
|
scroller_width,
|
||||||
|
|
@ -1428,10 +1497,15 @@ impl Scrollbars {
|
||||||
};
|
};
|
||||||
|
|
||||||
let ratio = bounds.width / content_bounds.width;
|
let ratio = bounds.width / content_bounds.width;
|
||||||
|
|
||||||
|
let scroller = if ratio >= 1.0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
// min width for easier grabbing with extra wide content
|
// min width for easier grabbing with extra wide content
|
||||||
let scroller_length = (scrollbar_bounds.width * ratio).max(2.0);
|
let scroller_length = (scrollbar_bounds.width * ratio).max(2.0);
|
||||||
let scroller_offset =
|
let scroller_offset =
|
||||||
translation.x * ratio * scrollbar_bounds.width / bounds.width;
|
translation.x * ratio * scrollbar_bounds.width
|
||||||
|
/ bounds.width;
|
||||||
|
|
||||||
let scroller_bounds = Rectangle {
|
let scroller_bounds = Rectangle {
|
||||||
x: (scrollbar_bounds.x + scroller_offset).max(0.0),
|
x: (scrollbar_bounds.x + scroller_offset).max(0.0),
|
||||||
|
|
@ -1442,12 +1516,15 @@ impl Scrollbars {
|
||||||
height: scroller_width,
|
height: scroller_width,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Some(internals::Scroller {
|
||||||
|
bounds: scroller_bounds,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
Some(internals::Scrollbar {
|
Some(internals::Scrollbar {
|
||||||
total_bounds: total_scrollbar_bounds,
|
total_bounds: total_scrollbar_bounds,
|
||||||
bounds: scrollbar_bounds,
|
bounds: scrollbar_bounds,
|
||||||
scroller: internals::Scroller {
|
scroller,
|
||||||
bounds: scroller_bounds,
|
|
||||||
},
|
|
||||||
alignment: horizontal.alignment,
|
alignment: horizontal.alignment,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1478,33 +1555,33 @@ impl Scrollbars {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> {
|
fn grab_y_scroller(&self, cursor_position: Point) -> Option<f32> {
|
||||||
self.y.and_then(|scrollbar| {
|
let scrollbar = self.y?;
|
||||||
|
let scroller = scrollbar.scroller?;
|
||||||
|
|
||||||
if scrollbar.total_bounds.contains(cursor_position) {
|
if scrollbar.total_bounds.contains(cursor_position) {
|
||||||
Some(if scrollbar.scroller.bounds.contains(cursor_position) {
|
Some(if scroller.bounds.contains(cursor_position) {
|
||||||
(cursor_position.y - scrollbar.scroller.bounds.y)
|
(cursor_position.y - scroller.bounds.y) / scroller.bounds.height
|
||||||
/ scrollbar.scroller.bounds.height
|
|
||||||
} else {
|
} else {
|
||||||
0.5
|
0.5
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn grab_x_scroller(&self, cursor_position: Point) -> Option<f32> {
|
fn grab_x_scroller(&self, cursor_position: Point) -> Option<f32> {
|
||||||
self.x.and_then(|scrollbar| {
|
let scrollbar = self.x?;
|
||||||
|
let scroller = scrollbar.scroller?;
|
||||||
|
|
||||||
if scrollbar.total_bounds.contains(cursor_position) {
|
if scrollbar.total_bounds.contains(cursor_position) {
|
||||||
Some(if scrollbar.scroller.bounds.contains(cursor_position) {
|
Some(if scroller.bounds.contains(cursor_position) {
|
||||||
(cursor_position.x - scrollbar.scroller.bounds.x)
|
(cursor_position.x - scroller.bounds.x) / scroller.bounds.width
|
||||||
/ scrollbar.scroller.bounds.width
|
|
||||||
} else {
|
} else {
|
||||||
0.5
|
0.5
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active(&self) -> bool {
|
fn active(&self) -> bool {
|
||||||
|
|
@ -1521,7 +1598,7 @@ pub(super) mod internals {
|
||||||
pub struct Scrollbar {
|
pub struct Scrollbar {
|
||||||
pub total_bounds: Rectangle,
|
pub total_bounds: Rectangle,
|
||||||
pub bounds: Rectangle,
|
pub bounds: Rectangle,
|
||||||
pub scroller: Scroller,
|
pub scroller: Option<Scroller>,
|
||||||
pub alignment: Alignment,
|
pub alignment: Alignment,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1537,15 +1614,19 @@ pub(super) mod internals {
|
||||||
grabbed_at: f32,
|
grabbed_at: f32,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
|
if let Some(scroller) = self.scroller {
|
||||||
let percentage = (cursor_position.y
|
let percentage = (cursor_position.y
|
||||||
- self.bounds.y
|
- self.bounds.y
|
||||||
- self.scroller.bounds.height * grabbed_at)
|
- scroller.bounds.height * grabbed_at)
|
||||||
/ (self.bounds.height - self.scroller.bounds.height);
|
/ (self.bounds.height - scroller.bounds.height);
|
||||||
|
|
||||||
match self.alignment {
|
match self.alignment {
|
||||||
Alignment::Start => percentage,
|
Alignment::Start => percentage,
|
||||||
Alignment::End => 1.0 - percentage,
|
Alignment::End => 1.0 - percentage,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the x-axis scrolled percentage from the cursor position.
|
/// Returns the x-axis scrolled percentage from the cursor position.
|
||||||
|
|
@ -1554,15 +1635,19 @@ pub(super) mod internals {
|
||||||
grabbed_at: f32,
|
grabbed_at: f32,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
) -> f32 {
|
) -> f32 {
|
||||||
|
if let Some(scroller) = self.scroller {
|
||||||
let percentage = (cursor_position.x
|
let percentage = (cursor_position.x
|
||||||
- self.bounds.x
|
- self.bounds.x
|
||||||
- self.scroller.bounds.width * grabbed_at)
|
- scroller.bounds.width * grabbed_at)
|
||||||
/ (self.bounds.width - self.scroller.bounds.width);
|
/ (self.bounds.width - scroller.bounds.width);
|
||||||
|
|
||||||
match self.alignment {
|
match self.alignment {
|
||||||
Alignment::Start => percentage,
|
Alignment::Start => percentage,
|
||||||
Alignment::End => 1.0 - percentage,
|
Alignment::End => 1.0 - percentage,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1595,22 +1680,22 @@ pub enum Status {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The appearance of a scrolable.
|
/// The appearance of a scrollable.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Style {
|
pub struct Style {
|
||||||
/// The [`container::Style`] of a scrollable.
|
/// The [`container::Style`] of a scrollable.
|
||||||
pub container: container::Style,
|
pub container: container::Style,
|
||||||
/// The vertical [`Scrollbar`] appearance.
|
/// The vertical [`Rail`] appearance.
|
||||||
pub vertical_scrollbar: Scrollbar,
|
pub vertical_rail: Rail,
|
||||||
/// The horizontal [`Scrollbar`] appearance.
|
/// The horizontal [`Rail`] appearance.
|
||||||
pub horizontal_scrollbar: Scrollbar,
|
pub horizontal_rail: Rail,
|
||||||
/// The [`Background`] of the gap between a horizontal and vertical scrollbar.
|
/// The [`Background`] of the gap between a horizontal and vertical scrollbar.
|
||||||
pub gap: Option<Background>,
|
pub gap: Option<Background>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The appearance of the scrollbar of a scrollable.
|
/// The appearance of the scrollbar of a scrollable.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Scrollbar {
|
pub struct Rail {
|
||||||
/// The [`Background`] of a scrollbar.
|
/// The [`Background`] of a scrollbar.
|
||||||
pub background: Option<Background>,
|
pub background: Option<Background>,
|
||||||
/// The [`Border`] of a scrollbar.
|
/// The [`Border`] of a scrollbar.
|
||||||
|
|
@ -1659,7 +1744,7 @@ impl Catalog for Theme {
|
||||||
pub fn default(theme: &Theme, status: Status) -> Style {
|
pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
let palette = theme.extended_palette();
|
let palette = theme.extended_palette();
|
||||||
|
|
||||||
let scrollbar = Scrollbar {
|
let scrollbar = Rail {
|
||||||
background: Some(palette.background.weak.color.into()),
|
background: Some(palette.background.weak.color.into()),
|
||||||
border: Border::rounded(2),
|
border: Border::rounded(2),
|
||||||
scroller: Scroller {
|
scroller: Scroller {
|
||||||
|
|
@ -1671,15 +1756,15 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
match status {
|
match status {
|
||||||
Status::Active => Style {
|
Status::Active => Style {
|
||||||
container: container::Style::default(),
|
container: container::Style::default(),
|
||||||
vertical_scrollbar: scrollbar,
|
vertical_rail: scrollbar,
|
||||||
horizontal_scrollbar: scrollbar,
|
horizontal_rail: scrollbar,
|
||||||
gap: None,
|
gap: None,
|
||||||
},
|
},
|
||||||
Status::Hovered {
|
Status::Hovered {
|
||||||
is_horizontal_scrollbar_hovered,
|
is_horizontal_scrollbar_hovered,
|
||||||
is_vertical_scrollbar_hovered,
|
is_vertical_scrollbar_hovered,
|
||||||
} => {
|
} => {
|
||||||
let hovered_scrollbar = Scrollbar {
|
let hovered_scrollbar = Rail {
|
||||||
scroller: Scroller {
|
scroller: Scroller {
|
||||||
color: palette.primary.strong.color,
|
color: palette.primary.strong.color,
|
||||||
..scrollbar.scroller
|
..scrollbar.scroller
|
||||||
|
|
@ -1689,12 +1774,12 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
|
|
||||||
Style {
|
Style {
|
||||||
container: container::Style::default(),
|
container: container::Style::default(),
|
||||||
vertical_scrollbar: if is_vertical_scrollbar_hovered {
|
vertical_rail: if is_vertical_scrollbar_hovered {
|
||||||
hovered_scrollbar
|
hovered_scrollbar
|
||||||
} else {
|
} else {
|
||||||
scrollbar
|
scrollbar
|
||||||
},
|
},
|
||||||
horizontal_scrollbar: if is_horizontal_scrollbar_hovered {
|
horizontal_rail: if is_horizontal_scrollbar_hovered {
|
||||||
hovered_scrollbar
|
hovered_scrollbar
|
||||||
} else {
|
} else {
|
||||||
scrollbar
|
scrollbar
|
||||||
|
|
@ -1706,7 +1791,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
is_horizontal_scrollbar_dragged,
|
is_horizontal_scrollbar_dragged,
|
||||||
is_vertical_scrollbar_dragged,
|
is_vertical_scrollbar_dragged,
|
||||||
} => {
|
} => {
|
||||||
let dragged_scrollbar = Scrollbar {
|
let dragged_scrollbar = Rail {
|
||||||
scroller: Scroller {
|
scroller: Scroller {
|
||||||
color: palette.primary.base.color,
|
color: palette.primary.base.color,
|
||||||
..scrollbar.scroller
|
..scrollbar.scroller
|
||||||
|
|
@ -1716,12 +1801,12 @@ pub fn default(theme: &Theme, status: Status) -> Style {
|
||||||
|
|
||||||
Style {
|
Style {
|
||||||
container: container::Style::default(),
|
container: container::Style::default(),
|
||||||
vertical_scrollbar: if is_vertical_scrollbar_dragged {
|
vertical_rail: if is_vertical_scrollbar_dragged {
|
||||||
dragged_scrollbar
|
dragged_scrollbar
|
||||||
} else {
|
} else {
|
||||||
scrollbar
|
scrollbar
|
||||||
},
|
},
|
||||||
horizontal_scrollbar: if is_horizontal_scrollbar_dragged {
|
horizontal_rail: if is_horizontal_scrollbar_dragged {
|
||||||
dragged_scrollbar
|
dragged_scrollbar
|
||||||
} else {
|
} else {
|
||||||
scrollbar
|
scrollbar
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue