use iced::widget::{Space, scrollable};
use iced::{Element, Theme, widget};
use serde_json::Value;
use crate::PlushieRenderer;
use crate::iced_convert;
use crate::message::Message;
use crate::protocol::TreeNode;
use crate::registry::PlushieWidget;
use crate::render_ctx::RenderCtx;
use crate::widget::helpers::*;
use plushie_core::types::{Anchor, Color, Direction, Length, PlushieType};
fn apply_scrollable_chrome(
style: &mut scrollable::Style,
scrollbar_color: Option<iced::Color>,
scroller_color: Option<iced::Color>,
) {
if let Some(sc) = scrollbar_color {
style.vertical_rail.background = Some(iced::Background::Color(sc));
style.horizontal_rail.background = Some(iced::Background::Color(sc));
}
if let Some(sc) = scroller_color {
style.vertical_rail.scroller.background = iced::Background::Color(sc);
style.horizontal_rail.scroller.background = iced::Background::Color(sc);
}
}
struct ScrollableProps {
width: Option<Length>,
height: Option<Length>,
direction: Option<Direction>,
scrollbar_width: Option<f32>,
scrollbar_margin: Option<f32>,
scroller_width: Option<f32>,
anchor: Option<Anchor>,
on_scroll: Option<bool>,
auto_scroll: Option<bool>,
scrollbar_color: Option<Color>,
scroller_color: Option<Color>,
}
impl ScrollableProps {
fn from_node(node: &TreeNode) -> Self {
let p = &node.props;
Self {
width: Length::extract(p, "width"),
height: Length::extract(p, "height"),
direction: Direction::extract(p, "direction"),
scrollbar_width: f32::extract(p, "scrollbar_width"),
scrollbar_margin: f32::extract(p, "scrollbar_margin"),
scroller_width: f32::extract(p, "scroller_width"),
anchor: Anchor::extract(p, "anchor"),
on_scroll: bool::extract(p, "on_scroll"),
auto_scroll: bool::extract(p, "auto_scroll"),
scrollbar_color: Color::extract(p, "scrollbar_color"),
scroller_color: Color::extract(p, "scroller_color"),
}
}
fn build_scrollbar(&self) -> scrollable::Scrollbar {
let mut sb = scrollable::Scrollbar::default();
if let Some(w) = self.scrollbar_width {
sb = sb.width(w);
}
if let Some(m) = self.scrollbar_margin {
sb = sb.margin(m);
}
if let Some(sw) = self.scroller_width {
sb = sb.scroller_width(sw);
}
sb
}
}
pub(crate) struct ScrollableWidget;
impl<R: PlushieRenderer> PlushieWidget<R> for ScrollableWidget {
fn type_names(&self) -> &[&str] {
&["scrollable"]
}
fn render<'a>(
&'a self,
node: &'a TreeNode,
ctx: &RenderCtx<'a, R>,
) -> Element<'a, Message, Theme, R> {
let sp = ScrollableProps::from_node(node);
let spacing = prop_animated_f32(
&ctx.caches.interpolated_props,
&node.id,
&node.props,
"spacing",
);
let child: Element<'a, Message, Theme, R> = node
.children
.first()
.map(|c| ctx.render_child(c))
.unwrap_or_else(|| Space::new().into());
let sb = sp.build_scrollbar();
let direction = sp.direction.unwrap_or(Direction::Vertical);
let mut s = scrollable(child).direction(iced_convert::scrollable_direction(direction, sb));
let width = sp
.width
.as_ref()
.map(iced_convert::length)
.unwrap_or(iced::Length::Shrink);
let height = sp
.height
.as_ref()
.map(iced_convert::length)
.unwrap_or(iced::Length::Shrink);
s = s.width(width).height(height);
s = s.id(widget::Id::from(node.id.clone()));
if let Some(spacing_val) = spacing {
s = s.spacing(spacing_val);
}
if let Some(a) = sp.anchor {
s = s.anchor_y(iced_convert::anchor(a));
}
if sp.on_scroll.unwrap_or(false) {
let window_id = ctx.window_id.to_string();
let scroll_id = node.id.clone();
s = s.on_scroll(move |viewport| {
let abs = viewport.absolute_offset();
let rel = viewport.relative_offset();
let bounds = viewport.bounds();
let content_bounds = viewport.content_bounds();
Message::Event {
window_id: window_id.clone(),
id: scroll_id.clone(),
value: serde_json::json!({
"absolute_x": abs.x, "absolute_y": abs.y,
"relative_x": rel.x, "relative_y": rel.y,
"bounds_width": bounds.width, "bounds_height": bounds.height,
"content_width": content_bounds.width, "content_height": content_bounds.height,
}),
family: "scrolled".into(),
}
});
}
if sp.auto_scroll.unwrap_or(false) {
s = s.auto_scroll(true);
}
let scrollbar_color = sp
.scrollbar_color
.as_ref()
.map(iced_convert::color)
.or(ctx.theme_chrome.scrollbar_color);
let scroller_color = sp
.scroller_color
.as_ref()
.map(iced_convert::color)
.or(ctx.theme_chrome.scroller_color);
if scrollbar_color.is_some() || scroller_color.is_some() {
s = s.style(move |theme: &iced::Theme, status| {
let mut style = scrollable::default(theme, status);
apply_scrollable_chrome(&mut style, scrollbar_color, scroller_color);
style
});
}
{
let status_wid = ctx.window_id.to_string();
let status_id = node.id.clone();
s = s.on_status_change(move |status| Message::Event {
window_id: status_wid.clone(),
id: status_id.clone(),
value: Value::String(status.to_string()),
family: "status".into(),
});
}
s.into()
}
fn fresh_for_session(&self) -> Box<dyn PlushieWidget<R>> {
Box::new(ScrollableWidget)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn apply_scrollable_chrome_sets_both_axes() {
let rail = iced::Color::from_rgb8(0x11, 0x22, 0x33);
let handle = iced::Color::from_rgb8(0x44, 0x55, 0x66);
let mut style = scrollable::default(
&iced::Theme::Dark,
scrollable::Status::Active {
is_horizontal_scrollbar_disabled: false,
is_vertical_scrollbar_disabled: false,
},
);
apply_scrollable_chrome(&mut style, Some(rail), Some(handle));
assert_eq!(
style.vertical_rail.background,
Some(iced::Background::Color(rail))
);
assert_eq!(
style.horizontal_rail.background,
Some(iced::Background::Color(rail))
);
assert_eq!(
style.vertical_rail.scroller.background,
iced::Background::Color(handle)
);
assert_eq!(
style.horizontal_rail.scroller.background,
iced::Background::Color(handle)
);
}
}