Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ You can find its changes [documented below](#070---2021-01-01).

- Warn on unhandled Commands ([#1533] by [@Maan2003])
- `WindowDesc::new` takes the root widget directly instead of a closure ([#1559] by [@lassipulkkinen])
- Spacers in `Flex` are now implemented by calculating the space in `Flex` instead of creating a widget for it ([#1584] by [@JAicewizard])

### Deprecated

Expand Down
266 changes: 149 additions & 117 deletions druid/src/widget/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

use crate::kurbo::common::FloatExt;
use crate::widget::prelude::*;
use crate::widget::SizedBox;
use crate::{Data, KeyOrValue, Point, Rect, WidgetPod};

/// A container with either horizontal or vertical layout.
Expand Down Expand Up @@ -140,18 +139,7 @@ pub struct Flex<T> {
cross_alignment: CrossAxisAlignment,
main_alignment: MainAxisAlignment,
fill_major_axis: bool,
children: Vec<ChildWidget<T>>,
}

struct ChildWidget<T> {
widget: WidgetPod<T, Box<dyn Widget<T>>>,
params: FlexParams,
}

/// A dummy widget we use to do spacing.
struct Spacer {
axis: Axis,
len: KeyOrValue<f64>,
children: Vec<Child<T>>,
}

/// Optional parameters for an item in a [`Flex`] container (row or column).
Expand Down Expand Up @@ -361,15 +349,6 @@ impl FlexParams {
}
}

impl<T> ChildWidget<T> {
fn new(child: impl Widget<T> + 'static, params: FlexParams) -> Self {
ChildWidget {
widget: WidgetPod::new(Box::new(child)),
params,
}
}
}

impl<T: Data> Flex<T> {
/// Create a new Flex oriented along the provided axis.
pub fn for_axis(axis: Axis) -> Self {
Expand Down Expand Up @@ -560,7 +539,16 @@ impl<T: Data> Flex<T> {
child: impl Widget<T> + 'static,
params: impl Into<FlexParams>,
) {
let child = ChildWidget::new(child, params.into());
let params = params.into();
let child = if params.flex == 0.0 {
Child::Fixed(WidgetPod::new(Box::new(child)), params.alignment)
} else {
Child::Flex(
WidgetPod::new(Box::new(child)),
params.alignment,
params.flex,
)
};
self.children.push(child);
}

Expand All @@ -583,39 +571,49 @@ impl<T: Data> Flex<T> {
///
/// [`add_default_spacer`]: #method.add_default_spacer
pub fn add_spacer(&mut self, len: impl Into<KeyOrValue<f64>>) {
let spacer = Spacer {
axis: self.direction,
len: len.into(),
};
self.add_flex_child(spacer, 0.0);
let value = len.into();
let new_child = Child::FixedSpacer(value, 0.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 child = match self.direction {
Axis::Vertical => SizedBox::empty().expand_height(),
Axis::Horizontal => SizedBox::empty().expand_width(),
};
self.add_flex_child(child, flex);
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, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
for child in &mut self.children {
child.widget.event(ctx, event, data, env);
match child {
Child::Fixed(widget, _) | Child::Flex(widget, _, _) => {
widget.event(ctx, event, data, env)
}
_ => {}
}
}
}

fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
for child in &mut self.children {
child.widget.lifecycle(ctx, event, data, env);
match child {
Child::Fixed(widget, _) | Child::Flex(widget, _, _) => {
widget.lifecycle(ctx, event, data, env)
}
_ => {}
}
}
}

fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &T, data: &T, env: &Env) {
for child in &mut self.children {
child.widget.update(ctx, data, env);
match child {
Child::Fixed(widget, _) | Child::Flex(widget, _, _) => {
widget.update(ctx, data, env)
}
_ => {}
}
}
}

Expand All @@ -633,55 +631,71 @@ impl<T: Data> Widget<T> for Flex<T> {

// Measure non-flex children.
let mut major_non_flex = 0.0;
let mut flex_sum = 0.0;
for child in &mut self.children {
any_use_baseline &= child.params.alignment == Some(CrossAxisAlignment::Baseline);
match child {
Child::Fixed(widget, alignment) => {
any_use_baseline &= *alignment == Some(CrossAxisAlignment::Baseline);

let child_bc =
self.direction
.constraints(&loosened_bc, 0.0, std::f64::INFINITY);
let child_size = widget.layout(ctx, &child_bc, data, env);
let baseline_offset = widget.baseline_offset();

if child_size.width.is_infinite() {
log::warn!("A non-Flex child has an infinite width.");
}

if child.params.flex == 0.0 {
let child_bc = self
.direction
.constraints(&loosened_bc, 0., std::f64::INFINITY);
let child_size = child.widget.layout(ctx, &child_bc, data, env);
let baseline_offset = child.widget.baseline_offset();
if child_size.height.is_infinite() {
log::warn!("A non-Flex child has an infinite height.");
}

if child_size.width.is_infinite() {
log::warn!("A non-Flex child has an infinite width.");
major_non_flex += self.direction.major(child_size).expand();
minor = minor.max(self.direction.minor(child_size).expand());
max_above_baseline =
max_above_baseline.max(child_size.height - baseline_offset);
max_below_baseline = max_below_baseline.max(baseline_offset);
}

if child_size.height.is_infinite() {
log::warn!("A non-Flex child has an infinite height.");
Child::FixedSpacer(kv, calculated_siz) => {
*calculated_siz = kv.resolve(env);
major_non_flex += *calculated_siz;
}

major_non_flex += self.direction.major(child_size).expand();
minor = minor.max(self.direction.minor(child_size).expand());
max_above_baseline = max_above_baseline.max(child_size.height - baseline_offset);
max_below_baseline = max_below_baseline.max(baseline_offset);
Child::Flex(_, _, flex) | Child::FlexedSpacer(flex, _) => flex_sum += *flex,
}
}

let total_major = self.direction.major(bc.max());
let remaining = (total_major - major_non_flex).max(0.0);
let mut remainder: f64 = 0.0;
let flex_sum: f64 = self.children.iter().map(|child| child.params.flex).sum();
let mut major_flex: f64 = 0.0;

let mut major_flex: f64 = 0.0;
let px_per_flex = remaining / flex_sum;
// Measure flex children.
for child in &mut self.children {
if child.params.flex != 0.0 {
let desired_major = remaining * child.params.flex / flex_sum + remainder;
let actual_major = desired_major.round();
remainder = desired_major - actual_major;
let min_major = 0.0;

let child_bc = self
.direction
.constraints(&loosened_bc, min_major, actual_major);
let child_size = child.widget.layout(ctx, &child_bc, data, env);
let baseline_offset = child.widget.baseline_offset();

major_flex += self.direction.major(child_size).expand();
minor = minor.max(self.direction.minor(child_size).expand());
max_above_baseline = max_above_baseline.max(child_size.height - baseline_offset);
max_below_baseline = max_below_baseline.max(baseline_offset);
match child {
Child::Flex(widget, _, flex) => {
let desired_major = (*flex) * px_per_flex + remainder;
let actual_major = desired_major.round();
remainder = desired_major - actual_major;

let child_bc = self.direction.constraints(&loosened_bc, 0.0, actual_major);
let child_size = widget.layout(ctx, &child_bc, data, env);
let baseline_offset = widget.baseline_offset();

major_flex += self.direction.major(child_size).expand();
minor = minor.max(self.direction.minor(child_size).expand());
max_above_baseline =
max_above_baseline.max(child_size.height - baseline_offset);
max_below_baseline = max_below_baseline.max(baseline_offset);
}
Child::FlexedSpacer(flex, calculated_size) => {
let desired_major = (*flex) * px_per_flex + remainder;
*calculated_size = desired_major.round();
remainder = desired_major - *calculated_size;
major_flex += *calculated_size;
}
_ => {}
}
}

Expand All @@ -703,40 +717,52 @@ impl<T: Data> Widget<T> for Flex<T> {
_ => minor,
};

let extra_height = minor - minor_dim.min(minor);

let mut major = spacing.next().unwrap_or(0.);
let mut child_paint_rect = Rect::ZERO;

for child in &mut self.children {
let child_size = child.widget.layout_rect().size();
let alignment = child.params.alignment.unwrap_or(self.cross_alignment);
let child_minor_offset = match alignment {
// This will ignore baseline alignment if it is overridden on children,
// but is not the default for the container. Is this okay?
CrossAxisAlignment::Baseline if matches!(self.direction, Axis::Horizontal) => {
let extra_height = minor - minor_dim.min(minor);
let child_baseline = child.widget.baseline_offset();
let child_above_baseline = child_size.height - child_baseline;
extra_height + (max_above_baseline - child_above_baseline)
}
CrossAxisAlignment::Fill => {
let fill_size: Size = self
.direction
.pack(self.direction.major(child_size), minor_dim)
.into();
let child_bc = BoxConstraints::tight(fill_size);
child.widget.layout(ctx, &child_bc, data, env);
0.0
match child {
Child::Fixed(widget, alignment) | Child::Flex(widget, alignment, _) => {
let child_size = widget.layout_rect().size();
let alignment = alignment.unwrap_or(self.cross_alignment);
let child_minor_offset = match alignment {
// This will ignore baseline alignment if it is overridden on children,
// but is not the default for the container. Is this okay?
CrossAxisAlignment::Baseline
if matches!(self.direction, Axis::Horizontal) =>
{
let child_baseline = widget.baseline_offset();
let child_above_baseline = child_size.height - child_baseline;
extra_height + (max_above_baseline - child_above_baseline)
}
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(ctx, &child_bc, data, env);
0.0
}
_ => {
let extra_minor = minor_dim - self.direction.minor(child_size);
alignment.align(extra_minor)
}
};

let child_pos: Point = self.direction.pack(major, child_minor_offset).into();
widget.set_origin(ctx, data, env, child_pos);
child_paint_rect = child_paint_rect.union(widget.paint_rect());
major += self.direction.major(child_size).expand();
major += spacing.next().unwrap_or(0.);
}
_ => {
let extra_minor = minor_dim - self.direction.minor(child_size);
alignment.align(extra_minor)
Child::FlexedSpacer(_, calculated_size)
| Child::FixedSpacer(_, calculated_size) => {
major += *calculated_size;
}
};

let child_pos: Point = self.direction.pack(major, child_minor_offset).into();
child.widget.set_origin(ctx, data, env, child_pos);
child_paint_rect = child_paint_rect.union(child.widget.paint_rect());
major += self.direction.major(child_size).expand();
major += spacing.next().unwrap_or(0.);
}
}

if flex_sum > 0.0 && total_major.is_infinite() {
Expand Down Expand Up @@ -768,11 +794,14 @@ impl<T: Data> Widget<T> for Flex<T> {
Axis::Vertical => self
.children
.last()
.map(|last| {
let child_bl = last.widget.baseline_offset();
let child_max_y = last.widget.layout_rect().max_y();
let extra_bottom_padding = my_size.height - child_max_y;
child_bl + extra_bottom_padding
.map(|last| match last {
Child::Fixed(widget, _) | Child::Flex(widget, _, _) => {
let child_bl = widget.baseline_offset();
let child_max_y = widget.layout_rect().max_y();
let extra_bottom_padding = my_size.height - child_max_y;
child_bl + extra_bottom_padding
}
_ => 0.0,
})
.unwrap_or(0.0),
};
Expand All @@ -783,7 +812,10 @@ impl<T: Data> Widget<T> for Flex<T> {

fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
for child in &mut self.children {
child.widget.paint(ctx, data, env);
match child {
Child::Fixed(widget, _) | Child::Flex(widget, _, _) => widget.paint(ctx, data, env),
_ => {}
}
}

// paint the baseline if we're debugging layout
Expand Down Expand Up @@ -907,17 +939,6 @@ impl Iterator for Spacing {
}
}

impl<T: Data> Widget<T> for Spacer {
fn event(&mut self, _: &mut EventCtx, _: &Event, _: &mut T, _: &Env) {}
fn lifecycle(&mut self, _: &mut LifeCycleCtx, _: &LifeCycle, _: &T, _: &Env) {}
fn update(&mut self, _: &mut UpdateCtx, _: &T, _: &T, _: &Env) {}
fn layout(&mut self, _: &mut LayoutCtx, _: &BoxConstraints, _: &T, env: &Env) -> Size {
let major = self.len.resolve(env);
self.axis.pack(major, 0.0).into()
}
fn paint(&mut self, _: &mut PaintCtx, _: &T, _: &Env) {}
}

impl From<f64> for FlexParams {
fn from(flex: f64) -> FlexParams {
FlexParams {
Expand All @@ -927,6 +948,17 @@ impl From<f64> for FlexParams {
}
}

enum Child<T> {
Fixed(WidgetPod<T, Box<dyn Widget<T>>>, Option<CrossAxisAlignment>),
Flex(
WidgetPod<T, Box<dyn Widget<T>>>,
Option<CrossAxisAlignment>,
f64,
),
FixedSpacer(KeyOrValue<f64>, f64),
FlexedSpacer(f64, f64),
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down