Add support for embedded scrollbars for scrollable

Co-authored-by: dtzxporter <dtzxporter@users.noreply.github.com>
This commit is contained in:
Héctor Ramón Jiménez 2024-07-11 07:58:33 +02:00
parent 3c55e07668
commit 8ae4e09db9
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
4 changed files with 325 additions and 212 deletions

View file

@ -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

View file

@ -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())

View file

@ -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<_, _, _>);

View file

@ -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