use crate::kurbo::{Point, Rect, Size};
use crate::{
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, PaintCtx, UpdateCtx, Widget, WidgetPod,
};
pub struct Row;
pub struct Column;
pub struct Flex<T: Data> {
direction: Axis,
children: Vec<ChildWidget<T>>,
}
struct ChildWidget<T: Data> {
widget: WidgetPod<T, Box<dyn Widget<T>>>,
params: Params,
}
pub enum Axis {
Horizontal,
Vertical,
}
#[derive(Copy, Clone, Default)]
struct Params {
flex: f64,
}
impl Axis {
fn major(&self, coords: Size) -> f64 {
match *self {
Axis::Horizontal => coords.width,
Axis::Vertical => coords.height,
}
}
fn minor(&self, coords: Size) -> f64 {
match *self {
Axis::Horizontal => coords.height,
Axis::Vertical => coords.width,
}
}
fn pack(&self, major: f64, minor: f64) -> (f64, f64) {
match *self {
Axis::Horizontal => (major, minor),
Axis::Vertical => (minor, major),
}
}
}
impl Row {
#[deprecated(since = "0.4.0", note = "Use Flex::row() instead")]
pub fn new<T: Data>() -> Flex<T> {
Flex::row()
}
}
impl Column {
#[deprecated(since = "0.4.0", note = "Use Flex::column() instead")]
pub fn new<T: Data>() -> Flex<T> {
Flex::column()
}
}
impl<T: Data> Flex<T> {
pub fn row() -> Self {
Flex {
direction: Axis::Horizontal,
children: Vec::new(),
}
}
pub fn column() -> Self {
Flex {
direction: Axis::Vertical,
children: Vec::new(),
}
}
pub fn with_child(mut self, child: impl Widget<T> + 'static, flex: f64) -> Self {
self.add_child(child, flex);
self
}
pub fn add_child(&mut self, child: impl Widget<T> + 'static, flex: f64) {
let params = Params { flex };
let child = ChildWidget {
widget: WidgetPod::new(child).boxed(),
params,
};
self.children.push(child);
}
}
impl<T: Data> Widget<T> for Flex<T> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
for child in &mut self.children {
child.widget.event(ctx, event, data, env);
}
}
fn update(&mut self, ctx: &mut UpdateCtx, _old_data: Option<&T>, data: &T, env: &Env) {
for child in &mut self.children {
child.widget.update(ctx, data, env);
}
}
fn layout(
&mut self,
layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> Size {
bc.debug_check("Flex");
let mut total_non_flex = 0.0;
let mut minor = self.direction.minor(bc.min());
for child in &mut self.children {
if child.params.flex == 0.0 {
let child_bc = match self.direction {
Axis::Horizontal => BoxConstraints::new(
Size::new(0.0, bc.min().height),
Size::new(std::f64::INFINITY, bc.max().height),
),
Axis::Vertical => BoxConstraints::new(
Size::new(bc.min().width, 0.0),
Size::new(bc.max().width, std::f64::INFINITY),
),
};
let child_size = child.widget.layout(layout_ctx, &child_bc, data, env);
minor = minor.max(self.direction.minor(child_size));
total_non_flex += self.direction.major(child_size);
let rect = Rect::from_origin_size(Point::ORIGIN, child_size);
child.widget.set_layout_rect(rect);
}
}
let total_major = self.direction.major(bc.max());
let remaining = (total_major - total_non_flex).max(0.0);
let flex_sum: f64 = self.children.iter().map(|child| child.params.flex).sum();
for child in &mut self.children {
if child.params.flex != 0.0 {
let major = remaining * child.params.flex / flex_sum;
let min_major = if major.is_infinite() { 0.0 } else { major };
let child_bc = match self.direction {
Axis::Horizontal => BoxConstraints::new(
Size::new(min_major, bc.min().height),
Size::new(major, bc.max().height),
),
Axis::Vertical => BoxConstraints::new(
Size::new(bc.min().width, min_major),
Size::new(bc.max().width, major),
),
};
let child_size = child.widget.layout(layout_ctx, &child_bc, data, env);
minor = minor.max(self.direction.minor(child_size));
let rect = Rect::from_origin_size(Point::ORIGIN, child_size);
child.widget.set_layout_rect(rect);
}
}
let mut major = 0.0;
for child in &mut self.children {
let rect = child.widget.get_layout_rect();
let pos: Point = self.direction.pack(major, 0.0).into();
child.widget.set_layout_rect(rect.with_origin(pos));
major += self.direction.major(rect.size());
}
if flex_sum > 0.0 && total_major.is_infinite() {
log::warn!("A child of Flex is flex, but Flex is unbounded.")
}
if flex_sum > 0.0 {
major = total_major;
}
let (width, height) = self.direction.pack(major, minor);
Size::new(width, height)
}
fn paint(&mut self, paint_ctx: &mut PaintCtx, data: &T, env: &Env) {
for child in &mut self.children {
child.widget.paint_with_offset(paint_ctx, data, env);
}
}
}