use super::{Widget, element::Element, scroll_core};
use alloc::boxed::Box;
use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
use zest_core::{
Constraints, Length, RenderError, Renderer, ScrollDirection, ScrollMsg, ScrollState,
ScrollbarMode, SnapMode, TouchPhase, UiAction, WidgetId,
};
use zest_theme::Theme;
struct ScrollCore<'a, M> {
state: ScrollState,
dir: ScrollDirection,
bar: ScrollbarMode,
snap: SnapMode,
on_scroll: Option<Box<dyn Fn(ScrollMsg) -> M + 'a>>,
}
pub struct Container<'a, C: PixelColor, M: Clone> {
rect: Rectangle,
padding: u32,
child: Option<Element<'a, C, M>>,
width: Length,
height: Length,
scroll: Option<ScrollCore<'a, M>>,
content: Size,
}
impl<'a, C: PixelColor + 'a, M: Clone + 'a> Container<'a, C, M> {
pub fn new() -> Self {
Self {
rect: Rectangle::zero(),
padding: 0,
child: None,
width: Length::Fill,
height: Length::Fill,
scroll: None,
content: Size::zero(),
}
}
#[must_use]
pub fn padding(mut self, padding: u32) -> 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 child<W>(mut self, child: W) -> Self
where
W: Widget<C, M> + 'a,
{
self.child = Some(Element::new(child));
self
}
#[must_use]
pub fn scrollable(mut self, dir: ScrollDirection) -> Self {
let core = self.scroll.get_or_insert(ScrollCore {
state: ScrollState::new(),
dir,
bar: ScrollbarMode::Auto,
snap: SnapMode::None,
on_scroll: None,
});
core.dir = dir;
self
}
#[must_use]
pub fn scroll_state(mut self, state: &ScrollState) -> Self {
let core = self.scroll.get_or_insert(ScrollCore {
state: *state,
dir: ScrollDirection::Vertical,
bar: ScrollbarMode::Auto,
snap: SnapMode::None,
on_scroll: None,
});
core.state = *state;
self
}
#[must_use]
pub fn scrollbar(mut self, mode: ScrollbarMode) -> Self {
let core = self.scroll.get_or_insert(ScrollCore {
state: ScrollState::new(),
dir: ScrollDirection::Vertical,
bar: mode,
snap: SnapMode::None,
on_scroll: None,
});
core.bar = mode;
self
}
#[must_use]
pub fn snap(mut self, mode: SnapMode) -> Self {
let core = self.scroll.get_or_insert(ScrollCore {
state: ScrollState::new(),
dir: ScrollDirection::Vertical,
bar: ScrollbarMode::Auto,
snap: mode,
on_scroll: None,
});
core.snap = mode;
self
}
#[must_use]
pub fn on_scroll<F>(mut self, f: F) -> Self
where
F: Fn(ScrollMsg) -> M + 'a,
{
let core = self.scroll.get_or_insert(ScrollCore {
state: ScrollState::new(),
dir: ScrollDirection::Vertical,
bar: ScrollbarMode::Auto,
snap: SnapMode::None,
on_scroll: None,
});
core.on_scroll = Some(Box::new(f));
self
}
fn inner_rect(&self) -> Rectangle {
let pad = self.padding as i32;
let pad_u = self.padding;
Rectangle::new(
self.rect.top_left + Point::new(pad, pad),
Size::new(
self.rect.size.width.saturating_sub(pad_u * 2),
self.rect.size.height.saturating_sub(pad_u * 2),
),
)
}
fn snap_lines(&self) -> alloc::vec::Vec<i32> {
match &self.scroll {
Some(core) if core.snap != SnapMode::None => {
let viewport = self.inner_rect();
let off = scroll_core::render_offset(core.state, core.dir);
let rect = Rectangle::new(viewport.top_left, self.content);
scroll_core::snap_lines(
&[rect],
viewport.top_left,
off,
viewport.size,
core.dir,
core.snap,
)
}
_ => alloc::vec::Vec::new(),
}
}
fn arrange_scroll(&mut self, dir: ScrollDirection) {
let viewport = self.inner_rect();
let measure_w = if dir.scrolls_x() {
zest_core::UNBOUNDED
} else {
viewport.size.width
};
let measure_h = if dir.scrolls_y() {
zest_core::UNBOUNDED
} else {
viewport.size.height
};
let off = self
.scroll
.as_ref()
.map_or(Point::zero(), |c| scroll_core::render_offset(c.state, dir));
if let Some(child) = self.child.as_mut() {
let measured = child.measure(Constraints::loose(Size::new(measure_w, measure_h)));
let content = Size::new(
if dir.scrolls_x() {
measured.width.max(viewport.size.width)
} else {
viewport.size.width
},
if dir.scrolls_y() {
measured.height.max(viewport.size.height)
} else {
viewport.size.height
},
);
self.content = content;
child.arrange(Rectangle::new(viewport.top_left - off, content));
} else {
self.content = Size::zero();
}
}
}
impl<'a, C: PixelColor + 'a, M: Clone + 'a> Default for Container<'a, C, M> {
fn default() -> Self {
Self::new()
}
}
impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for Container<'a, C, M> {
fn measure(&mut self, constraints: Constraints) -> Size {
let dx = 2 * self.padding;
let dy = 2 * self.padding;
let inner = constraints.shrink(dx, dy);
let child_size = self
.child
.as_mut()
.map_or(Size::zero(), |c| c.measure(inner));
let intrinsic_w = child_size.width.saturating_add(dx);
let intrinsic_h = child_size.height.saturating_add(dy);
let w = self.width.resolve(intrinsic_w, constraints.max.width);
let h = self.height.resolve(intrinsic_h, constraints.max.height);
constraints.clamp(Size::new(w, h))
}
fn preferred_size(&self) -> (Length, Length) {
(self.width, self.height)
}
fn arrange(&mut self, rect: Rectangle) {
self.rect = rect;
match self.scroll.as_ref().map(|c| c.dir) {
Some(dir) if dir != ScrollDirection::None => self.arrange_scroll(dir),
_ => {
let inner = self.inner_rect();
if let Some(child) = self.child.as_mut() {
child.arrange(inner);
}
}
}
}
fn rect(&self) -> Rectangle {
self.rect
}
fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
match self.scroll.as_ref() {
Some(core) if core.dir != ScrollDirection::None => {
let dir = core.dir;
let state = core.state;
let viewport = self.inner_rect();
let content = self.content;
let lines = self.snap_lines();
let on_scroll = self.scroll.as_ref().and_then(|c| c.on_scroll.as_deref());
let child = &mut self.child;
scroll_core::route_touch(
state,
dir,
viewport,
content,
point,
phase,
&lines,
on_scroll,
|p, ph| child.as_mut().and_then(|c| c.handle_touch(p, ph)),
)
}
_ => self
.child
.as_mut()
.and_then(|child| child.handle_touch(point, phase)),
}
}
fn mark_pressed(&mut self, point: Point) {
if let Some(core) = self.scroll.as_ref() {
if matches!(
core.state.phase,
zest_core::GesturePhase::Dragging
| zest_core::GesturePhase::Flinging
| zest_core::GesturePhase::Springing
) {
return;
}
}
if let Some(child) = self.child.as_mut() {
child.mark_pressed(point);
}
}
fn collect_focusable(&self, out: &mut alloc::vec::Vec<WidgetId>) {
if let Some(child) = self.child.as_ref() {
child.collect_focusable(out);
}
}
fn sync_focus(&mut self, focused: Option<WidgetId>) {
if let Some(child) = self.child.as_mut() {
child.sync_focus(focused);
}
}
fn route_action(&mut self, target: WidgetId, action: UiAction) -> Option<M> {
self.child
.as_mut()
.and_then(|child| child.route_action(target, action))
}
fn navigate_focus(&self, target: WidgetId, action: UiAction) -> Option<WidgetId> {
self.child
.as_ref()
.and_then(|child| child.navigate_focus(target, action))
}
fn focus_rect(&self, target: WidgetId) -> Option<Rectangle> {
self.child
.as_ref()
.and_then(|child| child.focus_rect(target))
}
fn focus_at(&self, point: Point) -> Option<WidgetId> {
self.child.as_ref().and_then(|child| child.focus_at(point))
}
fn draw<'t>(
&self,
renderer: &mut dyn Renderer<C>,
theme: &Theme<'t, C>,
) -> Result<(), RenderError> {
match self.scroll.as_ref() {
Some(core) if core.dir != ScrollDirection::None => {
let viewport = self.inner_rect();
renderer.push_clip(viewport);
if let Some(child) = &self.child {
child.draw(renderer, theme)?;
}
renderer.pop_clip();
scroll_core::draw_scrollbars(
renderer,
theme,
core.state,
core.bar,
core.dir,
viewport,
self.content,
)?;
Ok(())
}
_ => {
if let Some(child) = &self.child {
child.draw(renderer, theme)?;
}
Ok(())
}
}
}
}