use frus_core::{Rect, Scene, SizeClass};
use frus_layout::{FlexDirection, Style};
use crate::flex::Flex;
use crate::interaction::Status;
use crate::theme::Theme;
use crate::widget::Widget;
pub struct TwoPane<Msg = crate::callback::Callback> {
class: SizeClass,
ratio: f32,
show_detail: bool,
list: Option<Box<dyn Widget<Msg>>>,
row: bool,
children: Vec<Box<dyn Widget<Msg>>>,
}
impl<Msg: Clone + 'static> TwoPane<Msg> {
pub fn new(class: SizeClass) -> Self {
Self {
class,
ratio: 0.38,
show_detail: false,
list: None,
row: false,
children: Vec::new(),
}
}
pub fn ratio(mut self, ratio: f32) -> Self {
self.ratio = ratio.clamp(0.1, 0.9);
self
}
pub fn show_detail(mut self, show: bool) -> Self {
self.show_detail = show;
self
}
pub fn list(mut self, list: impl Widget<Msg> + 'static) -> Self {
self.list = Some(Box::new(list));
self
}
pub fn detail(mut self, detail: impl Widget<Msg> + 'static) -> Self {
let detail: Box<dyn Widget<Msg>> = Box::new(detail);
if self.class == SizeClass::Expanded {
let list = self
.list
.take()
.expect("list() before detail() in side-by-side mode");
self.children = vec![
Box::new(Flex::column().flex(self.ratio).child(list)),
Box::new(Flex::column().flex(1.0 - self.ratio).child(detail)),
];
self.row = true;
} else {
let single = if self.show_detail {
detail
} else {
self.list
.take()
.expect("single-pane mode needs list() or show_detail")
};
self.children = vec![Box::new(Flex::column().flex(1.0).child(single))];
self.row = false;
}
self
}
}
impl<Msg: Clone> Widget<Msg> for TwoPane<Msg> {
fn style(&self) -> Style {
Style {
flex_direction: if self.row {
FlexDirection::Row
} else {
FlexDirection::Column
},
..Default::default()
}
}
fn fill_axes(&self, _theme: &Theme) -> crate::widget::FillAxes {
crate::widget::FillAxes::BOTH
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
fn on_click(&self) -> Option<Msg> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Container;
#[test]
fn expanded_shows_both_panes_side_by_side() {
let tp = TwoPane::<()>::new(SizeClass::Expanded)
.ratio(0.4)
.list(Container::new())
.detail(Container::new());
assert_eq!(Widget::<()>::style(&tp).flex_direction, FlexDirection::Row);
assert_eq!(Widget::<()>::children(&tp).len(), 2);
let panes = Widget::<()>::children(&tp);
assert_eq!(Widget::<()>::style(&*panes[0]).flex_grow, 0.4);
assert!((Widget::<()>::style(&*panes[1]).flex_grow - 0.6).abs() < 1e-6);
}
#[test]
fn compact_shows_a_single_pane() {
let list_only = TwoPane::<()>::new(SizeClass::Compact)
.list(Container::new())
.detail(Container::new());
assert_eq!(
Widget::<()>::style(&list_only).flex_direction,
FlexDirection::Column
);
assert_eq!(Widget::<()>::children(&list_only).len(), 1);
let detail_shown = TwoPane::<()>::new(SizeClass::Compact)
.show_detail(true)
.list(Container::new())
.detail(Container::new());
assert_eq!(Widget::<()>::children(&detail_shown).len(), 1);
}
}