use iced::widget::{container, scrollable, Column};
use iced::{Background, Border, Color, Element, Length, Theme};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ScrollDirection {
#[default]
Vertical,
Horizontal,
Both,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SnapAlignment {
#[default]
Start,
Center,
End,
}
pub struct ScrollableConfig {
pub direction: ScrollDirection,
pub width: Length,
pub height: Length,
pub scrollbar_width: f32,
pub scrollbar_margin: f32,
pub scroller_width: f32,
}
impl Default for ScrollableConfig {
fn default() -> Self {
Self {
direction: ScrollDirection::Vertical,
width: Length::Fill,
height: Length::Fill,
scrollbar_width: 10.0,
scrollbar_margin: 0.0,
scroller_width: 10.0,
}
}
}
pub fn styled_scrollable<'a, Message>(
content: impl Into<Element<'a, Message, Theme>>,
config: ScrollableConfig,
) -> Element<'a, Message, Theme>
where
Message: Clone + 'a,
{
let scroll = scrollable(content)
.width(config.width)
.height(config.height);
scroll.into()
}
pub struct AnchorSection<'a, Message> {
pub id: String,
pub content: Element<'a, Message, Theme>,
}
impl<'a, Message> AnchorSection<'a, Message> {
pub fn new(id: impl Into<String>, content: impl Into<Element<'a, Message, Theme>>) -> Self {
Self {
id: id.into(),
content: content.into(),
}
}
}
pub struct ScrollableBuilder<'a, Message> {
children: Vec<Element<'a, Message, Theme>>,
spacing: f32,
padding: f32,
width: Length,
height: Length,
}
impl<'a, Message: Clone + 'a> Default for ScrollableBuilder<'a, Message> {
fn default() -> Self {
Self::new()
}
}
impl<'a, Message: Clone + 'a> ScrollableBuilder<'a, Message> {
#[must_use]
pub fn new() -> Self {
Self {
children: Vec::new(),
spacing: 0.0,
padding: 0.0,
width: Length::Fill,
height: Length::Fill,
}
}
#[must_use]
pub fn push(mut self, content: impl Into<Element<'a, Message, Theme>>) -> Self {
self.children.push(content.into());
self
}
#[must_use]
pub fn push_anchor(
mut self,
_id: impl Into<String>,
content: impl Into<Element<'a, Message, Theme>>,
) -> Self {
let section = container(content)
.width(Length::Fill)
.into();
self.children.push(section);
self
}
#[must_use]
pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing;
self
}
#[must_use]
pub fn padding(mut self, padding: f32) -> Self {
self.padding = padding;
self
}
#[must_use]
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
self
}
#[must_use]
pub fn height(mut self, height: impl Into<Length>) -> Self {
self.height = height.into();
self
}
#[must_use]
pub fn build(self) -> Element<'a, Message, Theme> {
let content: Column<'a, Message, Theme> = Column::with_children(self.children)
.spacing(self.spacing)
.padding(self.padding)
.width(self.width);
scrollable(content)
.width(self.width)
.height(self.height)
.into()
}
}
pub fn themed_scrollable<'a, Message>(
content: impl Into<Element<'a, Message, Theme>>,
) -> scrollable::Scrollable<'a, Message, Theme>
where
Message: Clone + 'a,
{
scrollable(content)
.style(|theme, status| {
let palette = theme.extended_palette();
let rail_bg = palette.background.weak.color;
let scroller_color = match status {
scrollable::Status::Active => palette.background.strong.color,
scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => {
palette.primary.base.color
}
};
scrollable::Style {
container: container::Style::default(),
vertical_rail: scrollable::Rail {
background: Some(Background::Color(rail_bg)),
border: Border {
radius: 4.0.into(),
..Default::default()
},
scroller: scrollable::Scroller {
color: scroller_color,
border: Border {
radius: 4.0.into(),
..Default::default()
},
},
},
horizontal_rail: scrollable::Rail {
background: Some(Background::Color(rail_bg)),
border: Border {
radius: 4.0.into(),
..Default::default()
},
scroller: scrollable::Scroller {
color: scroller_color,
border: Border {
radius: 4.0.into(),
..Default::default()
},
},
},
gap: None,
}
})
}
pub fn minimal_scrollable<'a, Message>(
content: impl Into<Element<'a, Message, Theme>>,
) -> scrollable::Scrollable<'a, Message, Theme>
where
Message: Clone + 'a,
{
scrollable(content)
.style(|theme, status| {
let palette = theme.extended_palette();
let scroller_color = match status {
scrollable::Status::Active => Color {
a: 0.3,
..palette.background.strong.color
},
scrollable::Status::Hovered { .. } | scrollable::Status::Dragged { .. } => Color {
a: 0.6,
..palette.background.strong.color
},
};
scrollable::Style {
container: container::Style::default(),
vertical_rail: scrollable::Rail {
background: None,
border: Border::default(),
scroller: scrollable::Scroller {
color: scroller_color,
border: Border {
radius: 2.0.into(),
..Default::default()
},
},
},
horizontal_rail: scrollable::Rail {
background: None,
border: Border::default(),
scroller: scrollable::Scroller {
color: scroller_color,
border: Border {
radius: 2.0.into(),
..Default::default()
},
},
},
gap: None,
}
})
}
pub mod position {
use iced::widget::scrollable::{AbsoluteOffset, RelativeOffset};
#[must_use]
pub fn absolute(x: f32, y: f32) -> AbsoluteOffset {
AbsoluteOffset { x, y }
}
#[must_use]
pub fn relative(x: f32, y: f32) -> RelativeOffset {
RelativeOffset {
x: x.clamp(0.0, 1.0),
y: y.clamp(0.0, 1.0),
}
}
#[must_use]
pub fn top() -> AbsoluteOffset {
AbsoluteOffset { x: 0.0, y: 0.0 }
}
#[must_use]
pub fn bottom() -> RelativeOffset {
RelativeOffset { x: 0.0, y: 1.0 }
}
}