tuid/src/widget/flex/mod.rs

468 lines
14 KiB
Rust

// This is pretty much all stolen from linebender/druid.
// They have the license on their github repo.
// I didn't include it here because it might change-
// just go ask them what the license says and don't bother me.
use crate::{box_constraints::BoxConstraints, Data, DataWrapper, Event, Point, Rect, Size, Widget};
use super::WidgetPod;
mod axis;
mod child;
mod cross_axis_alignment;
mod flex_params;
mod main_axis_alignment;
mod spacing;
use axis::*;
use child::*;
use cross_axis_alignment::*;
use flex_params::*;
use main_axis_alignment::*;
use spacing::*;
pub struct Flex<T> {
direction: Axis,
cross_alignment: CrossAxisAlignment,
main_alignment: MainAxisAlignment,
fill_major_axis: bool,
children: Vec<Child<T>>,
}
impl<T: Data> Flex<T> {
/// Create a new Flex oriented along the provided axis.
pub fn for_axis(axis: Axis) -> Self {
Flex {
direction: axis,
children: Vec::new(),
cross_alignment: CrossAxisAlignment::Center,
main_alignment: MainAxisAlignment::Start,
fill_major_axis: false,
}
}
/// Create a new horizontal stack.
///
/// The child widgets are laid out horizontally, from left to right.
///
pub fn row() -> Self {
Self::for_axis(Axis::Horizontal)
}
/// Create a new vertical stack.
///
/// The child widgets are laid out vertically, from top to bottom.
pub fn column() -> Self {
Self::for_axis(Axis::Vertical)
}
/// Builder-style method for specifying the childrens' [`CrossAxisAlignment`].
///
/// [`CrossAxisAlignment`]: enum.CrossAxisAlignment.html
pub fn cross_axis_alignment(mut self, alignment: CrossAxisAlignment) -> Self {
self.cross_alignment = alignment;
self
}
/// Builder-style method for specifying the childrens' [`MainAxisAlignment`].
///
/// [`MainAxisAlignment`]: enum.MainAxisAlignment.html
pub fn main_axis_alignment(mut self, alignment: MainAxisAlignment) -> Self {
self.main_alignment = alignment;
self
}
/// Builder-style method for setting whether the container must expand
/// to fill the available space on its main axis.
///
/// If any children have flex then this container will expand to fill all
/// available space on its main axis; But if no children are flex,
/// this flag determines whether or not the container should shrink to fit,
/// or must expand to fill.
///
/// If it expands, and there is extra space left over, that space is
/// distributed in accordance with the [`MainAxisAlignment`].
///
/// The default value is `false`.
///
/// [`MainAxisAlignment`]: enum.MainAxisAlignment.html
pub fn must_fill_main_axis(mut self, fill: bool) -> Self {
self.fill_major_axis = fill;
self
}
/// Builder-style variant of `add_child`.
///
/// Convenient for assembling a group of widgets in a single expression.
pub fn with_child(mut self, child: impl Widget<T> + 'static) -> Self {
self.add_child(child);
self
}
/// Builder-style method to add a flexible child to the container.
///
/// This method is used when you need more control over the behaviour
/// of the widget you are adding. In the general case, this likely
/// means giving that child a 'flex factor', but it could also mean
/// giving the child a custom [`CrossAxisAlignment`], or a combination
/// of the two.
///
/// This function takes a child widget and [`FlexParams`]; importantly
/// you can pass in a float as your [`FlexParams`] in most cases.
///
/// For the non-builder varient, see [`add_flex_child`].
///
/// # Examples
///
/// ```
/// use druid::widget::{Flex, FlexParams, Label, Slider, CrossAxisAlignment};
///
/// let my_row = Flex::row()
/// .with_flex_child(Slider::new(), 1.0)
/// .with_flex_child(Slider::new(), FlexParams::new(1.0, CrossAxisAlignment::End));
/// ```
///
/// [`FlexParams`]: struct.FlexParams.html
/// [`add_flex_child`]: #method.add_flex_child
/// [`CrossAxisAlignment`]: enum.CrossAxisAlignment.html
pub fn with_flex_child(
mut self,
child: impl Widget<T> + 'static,
params: impl Into<FlexParams>,
) -> Self {
self.add_flex_child(child, params);
self
}
/// Builder-style method to add a spacer widget with a standard size.
///
/// The actual value of this spacer depends on whether this container is
/// a row or column, as well as theme settings.
pub fn with_default_spacer(mut self) -> Self {
self.add_default_spacer();
self
}
/// Builder-style method for adding a fixed-size spacer to the container.
///
/// If you are laying out standard controls in this container, you should
/// generally prefer to use [`add_default_spacer`].
///
/// [`add_default_spacer`]: #method.add_default_spacer
pub fn with_spacer(mut self, len: usize) -> Self {
self.add_spacer(len);
self
}
/// Builder-style method for adding a `flex` spacer to the container.
pub fn with_flex_spacer(mut self, flex: f64) -> Self {
self.add_flex_spacer(flex);
self
}
/// Set the childrens' [`CrossAxisAlignment`].
///
/// [`CrossAxisAlignment`]: enum.CrossAxisAlignment.html
pub fn set_cross_axis_alignment(&mut self, alignment: CrossAxisAlignment) {
self.cross_alignment = alignment;
}
/// Set the childrens' [`MainAxisAlignment`].
///
/// [`MainAxisAlignment`]: enum.MainAxisAlignment.html
pub fn set_main_axis_alignment(&mut self, alignment: MainAxisAlignment) {
self.main_alignment = alignment;
}
/// Set whether the container must expand to fill the available space on
/// its main axis.
pub fn set_must_fill_main_axis(&mut self, fill: bool) {
self.fill_major_axis = fill;
}
/// Add a non-flex child widget.
///
/// See also [`with_child`].
///
/// [`with_child`]: Flex::with_child
pub fn add_child(&mut self, child: impl Widget<T> + 'static) {
let child = Child::Fixed {
widget: WidgetPod::new(Box::new(child)),
alignment: None,
};
self.children.push(child);
}
/// Add a flexible child widget.
///
/// This method is used when you need more control over the behaviour
/// of the widget you are adding. In the general case, this likely
/// means giving that child a 'flex factor', but it could also mean
/// giving the child a custom [`CrossAxisAlignment`], or a combination
/// of the two.
///
/// This function takes a child widget and [`FlexParams`]; importantly
/// you can pass in a float as your [`FlexParams`] in most cases.
///
/// For the builder-style varient, see [`with_flex_child`].
///
/// # Examples
///
/// ```
/// use druid::widget::{Flex, FlexParams, Label, Slider, CrossAxisAlignment};
///
/// let mut my_row = Flex::row();
/// my_row.add_flex_child(Slider::new(), 1.0);
/// my_row.add_flex_child(Slider::new(), FlexParams::new(1.0, CrossAxisAlignment::End));
/// ```
///
/// [`with_flex_child`]: Flex::with_flex_child
pub fn add_flex_child(
&mut self,
child: impl Widget<T> + 'static,
params: impl Into<FlexParams>,
) {
let params = params.into();
let child = if params.flex > 0.0 {
Child::Flex {
widget: WidgetPod::new(Box::new(child)),
alignment: params.alignment,
flex: params.flex,
}
} else {
// tracing::warn!("Flex value should be > 0.0. To add a non-flex child use the add_child or with_child methods.\nSee the docs for more information: https://docs.rs/druid/0.7.0/druid/widget/struct.Flex.html");
Child::Fixed {
widget: WidgetPod::new(Box::new(child)),
alignment: None,
}
};
self.children.push(child);
}
/// Add a spacer widget with a standard size.
///
/// The actual value of this spacer depends on whether this container is
/// a row or column, as well as theme settings.
pub fn add_default_spacer(&mut self) {
let key = match self.direction {
Axis::Vertical => crate::theme::WIDGET_PADDING_VERTICAL,
Axis::Horizontal => crate::theme::WIDGET_PADDING_HORIZONTAL,
};
self.add_spacer(key);
}
/// Add an empty spacer widget with the given size.
///
/// If you are laying out standard controls in this container, you should
/// generally prefer to use [`add_default_spacer`].
///
/// [`add_default_spacer`]: Flex::add_default_spacer
pub fn add_spacer(&mut self, len: usize) {
let new_child = Child::FixedSpacer(len, 0);
self.children.push(new_child);
}
/// Add an empty spacer widget with a specific `flex` factor.
pub fn add_flex_spacer(&mut self, flex: f64) {
let flex = if flex >= 0.0 {
flex
} else {
debug_assert!(
flex >= 0.0,
"flex value for space should be greater than equal to 0, received: {}",
flex
);
// tracing::warn!("Provided flex value was less than 0: {}", flex);
0.0
};
let new_child = Child::FlexedSpacer(flex, 0.0);
self.children.push(new_child);
}
}
impl<T: Data> Widget<T> for Flex<T> {
fn event(&mut self, data: &mut DataWrapper<T>, event: &Event) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
child.event(data, event);
}
}
// #[instrument(name = "Flex", level = "trace", skip(self, ctx, event, data, env))]
// fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
// for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
// child.lifecycle(ctx, event, data, env);
// }
// }
fn update(&mut self, data: &DataWrapper<T>) {
for child in self.children.iter_mut() {
match child {
Child::Fixed { widget, .. } | Child::Flex { widget, .. } => widget.update(data),
_ => {}
}
}
}
fn layout(&mut self, bc: &BoxConstraints) -> Size {
// bc.debug_check("Flex");
// we loosen our constraints when passing to children.
let loosened_bc = bc.loosen();
// minor-axis values for all children
let mut minor = self.direction.minor(bc.min());
// Measure non-flex children.
let mut major_non_flex = 0;
let mut flex_sum = 0.0;
for child in &mut self.children {
match child {
Child::Fixed {
widget,
alignment: _,
} => {
// Get size of child (unbounded on major axis)
let child_bc = self.direction.constraints(&loosened_bc, 0, usize::MAX);
let child_size = widget.layout(&child_bc);
// Increment measurements
major_non_flex += self.direction.major(child_size);
minor = minor.max(self.direction.minor(child_size));
}
Child::FixedSpacer(kv, calculated_siz) => {
*calculated_siz = *kv;
major_non_flex += *calculated_siz;
}
Child::Flex { flex, .. } | Child::FlexedSpacer(flex, _) => flex_sum += *flex,
}
}
// Get the amount of space on the major axis
let total_major = self.direction.major(bc.max());
// Calculate the amount of space left on major axis (total - total-non-flex)
let remaining = total_major.saturating_sub(major_non_flex);
let mut remainder = 0.0;
let mut major_flex = 0.0;
let chars_per_flex = remaining as f64 / flex_sum;
// Measure flex children.
for child in &mut self.children {
match child {
Child::Flex { widget, flex, .. } => {
// This thing's flex represents a multiple of the number of chars per flex
let desired_major = (*flex) * chars_per_flex + remainder;
// Convert messy measurement to neat measurement
let actual_major = desired_major.round();
// Take difference of messy - neat and save it for next time
remainder = desired_major - actual_major;
// Get size of child (unbounded on major axis)
let child_bc = self
.direction
.constraints(&loosened_bc, 0, actual_major as usize);
// WidgetPods (which this child should be) cache their size.
let child_size = widget.layout(&child_bc);
// Increment measurements
major_flex += self.direction.major(child_size) as f64;
minor = minor.max(self.direction.minor(child_size));
}
Child::FlexedSpacer(flex, calculated_size) => {
// Do the same calculation as above (ie, decrement the amount of space left and increment the measurement)
let desired_major = (*flex) * chars_per_flex + remainder;
*calculated_size = desired_major.round();
remainder = desired_major - *calculated_size;
major_flex += *calculated_size;
}
_ => {}
}
}
// figure out if we have extra space on major axis, and if so how to use it
let extra = if self.fill_major_axis {
remaining.saturating_sub(major_flex as usize)
} else {
// if we are *not* expected to fill our available space this usually
// means we don't have any extra, unless dictated by our constraints.
self
.direction
.major(bc.min())
.saturating_sub(major_non_flex + major_flex as usize)
};
let mut spacing = Spacing::new(self.main_alignment, extra, self.children.len());
// the actual size needed to tightly fit the children on the minor axis.
// Unlike the 'minor' var, this ignores the incoming constraints.
let minor_dim = minor;
let mut major = spacing.next().unwrap_or(0);
let mut child_paint_rect = Rect::ZERO;
for child in &mut self.children {
match child {
Child::Fixed { widget, alignment }
| Child::Flex {
widget, alignment, ..
} => {
// Get the child's origin, origin + size rectangle
let child_size = widget.layout_rect().size();
// LEAVING OFF RIGHT HERE- DOCUMENT BELOW
let alignment = alignment.unwrap_or(self.cross_alignment);
let child_minor_offset = match alignment {
CrossAxisAlignment::Fill => {
let fill_size: Size = self
.direction
.pack(self.direction.major(child_size), minor_dim)
.into();
let child_bc = BoxConstraints::tight(fill_size);
widget.layout(&child_bc);
0.0
}
_ => {
let extra_minor = minor_dim - self.direction.minor(child_size);
alignment.align(extra_minor as f64)
}
};
let child_pos: Point = self
.direction
.pack(major as usize, child_minor_offset as usize)
.into();
widget.set_origin(child_pos);
child_paint_rect = child_paint_rect.union(widget.layout_rect());
major += self.direction.major(child_size);
major += spacing.next().unwrap_or(0);
}
Child::FlexedSpacer(_, calculated_size) => {
major += *calculated_size as usize;
}
Child::FixedSpacer(_, calculated_size) => {
major += *calculated_size;
}
}
}
if flex_sum > 0.0 {
major = total_major;
}
let my_size: Size = self.direction.pack(major, minor_dim).into();
// if we don't have to fill the main axis, we loosen that axis before constraining
if !self.fill_major_axis {
let max_major = self.direction.major(bc.max());
self
.direction
.constraints(bc, 0, max_major)
.constrain(my_size)
} else {
bc.constrain(my_size)
}
}
// #[instrument(name = "Flex", level = "trace", skip(self, ctx, data, env))]
fn paint(&mut self, buf: &mut [char], origin: Point, size: &Size) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
child.paint(buf, origin, size);
}
}
}