468 lines
14 KiB
Rust
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);
|
|
}
|
|
}
|
|
}
|