use ribir_core::prelude::*;
use smallvec::SmallVec;
#[derive(Clone, Copy, PartialEq)]
pub struct Expanded {
pub flex: f32,
pub defer_alloc: bool,
}
#[macro_export]
macro_rules! expanded {
($($t: tt)*) => { fn_widget! { @Expanded { $($t)* } } };
}
pub use expanded;
impl Default for Expanded {
fn default() -> Self { Self { flex: 1., defer_alloc: false } }
}
#[derive(Default)]
pub struct ExpandedDeclarer {
flex: Option<PipeValue<f32>>,
defer_alloc: Option<PipeValue<bool>>,
}
impl ExpandedDeclarer {
#[track_caller]
pub fn with_flex<K: ?Sized>(&mut self, flex: impl RInto<PipeValue<f32>, K>) -> &mut Self {
assert!(self.flex.is_none(), "`flex` is already set");
self.flex = Some(flex.r_into());
self
}
#[track_caller]
pub fn with_defer_alloc<K: ?Sized>(
&mut self, defer_alloc: impl RInto<PipeValue<bool>, K>,
) -> &mut Self {
assert!(self.defer_alloc.is_none(), "`defer_alloc` is already set");
self.defer_alloc = Some(defer_alloc.r_into());
self
}
}
impl Declare for Expanded {
type Builder = ExpandedDeclarer;
fn declarer() -> Self::Builder { ExpandedDeclarer::default() }
}
impl ObjDeclarer for ExpandedDeclarer {
type Target = DeclarerWithSubscription<Stateful<Expanded>>;
fn finish(self) -> Self::Target {
let (flex, u_flex) = self.flex.map_or((1., None), |v| v.unzip());
let (defer_alloc, u_defer_alloc) = self
.defer_alloc
.map_or((true, None), |v| v.unzip());
let host = Stateful::new(Expanded { flex, defer_alloc });
let mut subscribes = SmallVec::new();
if let Some(o) = u_flex {
let host = host.clone_writer();
let u = o.subscribe(move |v| host.write().flex = v);
subscribes.push(u)
}
if let Some(o) = u_defer_alloc {
let host = host.clone_writer();
let u = o.subscribe(move |v| host.write().defer_alloc = v);
subscribes.push(u)
}
DeclarerWithSubscription::new(host, subscribes)
}
}
impl<'c> ComposeChild<'c> for Expanded {
type Child = Widget<'c>;
fn compose_child(this: impl StateWriter<Value = Self>, mut child: Self::Child) -> Widget<'c> {
let data: Box<dyn Query> = match this.try_into_value() {
Ok(this) => Box::new(Queryable(this)),
Err(this) => {
child = child.dirty_on(this.raw_modifies(), DirtyPhase::Layout);
Box::new(this)
}
};
child.attach_data(data)
}
}
#[cfg(test)]
mod tests {
use ribir_core::{reset_test_env, test_helper::*};
use ribir_dev_helper::*;
use super::*;
use crate::prelude::*;
widget_layout_test!(
one_line_expanded,
WidgetTester::new(fn_widget! {
let size = Size::new(100., 50.);
@Flex {
@Expanded {
flex: 1.,
@Container { size }
}
@Container { size }
@Container { size }
@Expanded {
flex: 2.,
@Container { size }
}
}
})
.with_wnd_size(Size::new(500., 500.)),
LayoutCase::default().with_size(Size::new(500., 50.)),
LayoutCase::new(&[0, 0]).with_size(Size::new(100., 50.)),
LayoutCase::new(&[0, 1]).with_rect(ribir_types::rect(100., 0., 100., 50.)),
LayoutCase::new(&[0, 2]).with_rect(ribir_types::rect(200., 0., 100., 50.)),
LayoutCase::new(&[0, 3]).with_rect(ribir_types::rect(300., 0., 200., 50.))
);
widget_layout_test!(
wrap_expanded,
WidgetTester::new(fn_widget! {
let size = Size::new(100., 50.);
@Flex {
wrap: true,
@Expanded {
defer_alloc: false,
flex: 1. ,
@Container { size }
}
@Container { size }
@Container { size }
@Container { size }
@Container { size }
@Expanded {
defer_alloc: false,
flex: 1. ,
@Container { size, }
}
@Expanded {
defer_alloc: false,
flex: 4.,
@Container { size, }
}
}
})
.with_wnd_size(Size::new(350., 500.)),
LayoutCase::default().with_rect(ribir_types::rect(0., 0., 350., 150.)),
LayoutCase::new(&[0, 0]).with_rect(ribir_types::rect(0., 0., 150., 50.)),
LayoutCase::new(&[0, 1]).with_rect(ribir_types::rect(150., 0., 100., 50.)),
LayoutCase::new(&[0, 2]).with_rect(ribir_types::rect(250., 0., 100., 50.)),
LayoutCase::new(&[0, 3]).with_rect(ribir_types::rect(0., 50., 100., 50.)),
LayoutCase::new(&[0, 4]).with_rect(ribir_types::rect(100., 50., 100., 50.)),
LayoutCase::new(&[0, 5]).with_rect(ribir_types::rect(200., 50., 150., 50.)),
LayoutCase::new(&[0, 6]).with_rect(ribir_types::rect(0., 100., 350., 50.))
);
#[test]
fn modifies_flex() {
reset_test_env!();
let (flex, w_flex) = split_value(1f32);
let widget = fn_widget! {
let expanded = @Expanded { flex: 1. };
watch!(*$read(flex)).subscribe(move |val| $write(expanded).flex = val);
@Flex {
clamp: BoxClamp::EXPAND_X,
@(expanded) { @ { Void::default() } }
@Expanded {
flex: 1.,
@ { Void::default() }
}
@Container { size: Size::new(100., 100.) }
}
};
let wnd = TestWindow::new_with_size(widget, Size::new(400., 100.));
wnd.draw_frame();
LayoutCase::expect_size(&wnd, &[0, 0], Size::new(150., 0.));
*w_flex.write() = 2.;
wnd.draw_frame();
LayoutCase::expect_size(&wnd, &[0, 0], Size::new(200., 0.));
}
}