Skip to content

Commit

Permalink
Add the compute_max_intrinsic method to the Widget trait (linebender#…
Browse files Browse the repository at this point in the history
  • Loading branch information
sjoshid authored and xarvic committed Jul 29, 2022
1 parent 70ab369 commit 5bcf594
Show file tree
Hide file tree
Showing 12 changed files with 439 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ You can find its changes [documented below](#070---2021-01-01).
- `EventCtx::submit_notification_without_warning` ([#2141] by [@xarvic])
- `WidgetPod::requested_layout` ([#2145] by [@xarvic])
- Make `Parse` work better with floats and similar types ([#2148] by [@superfell])
- Added `compute_max_intrinsic` method to the `Widget` trait, which determines the maximum useful dimension of the widget ([#2172] by [@sjoshid])

### Changed

Expand Down Expand Up @@ -845,6 +846,7 @@ Last release without a changelog :(
[#2151]: https://github.com/linebender/druid/pull/2151
[#2157]: https://github.com/linebender/druid/pull/2157
[#2158]: https://github.com/linebender/druid/pull/2158
[#2172]: https://github.com/linebender/druid/pull/2172

[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
[0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0
Expand Down
48 changes: 48 additions & 0 deletions druid/src/box_constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//! The fundamental druid types.
use crate::kurbo::Size;
use crate::widget::Axis;

/// Constraints for layout.
///
Expand Down Expand Up @@ -270,6 +271,53 @@ impl BoxConstraints {
}
}
}

/// Sets the max on a given axis to infinity.
pub fn unbound_max(&self, axis: Axis) -> Self {
match axis {
Axis::Horizontal => self.unbound_max_width(),
Axis::Vertical => self.unbound_max_height(),
}
}

/// Sets max width to infinity.
pub fn unbound_max_width(&self) -> Self {
let mut max = self.max();
max.width = f64::INFINITY;
BoxConstraints::new(self.min(), max)
}

/// Sets max height to infinity.
pub fn unbound_max_height(&self) -> Self {
let mut max = self.max();
max.height = f64::INFINITY;
BoxConstraints::new(self.min(), max)
}

/// Shrinks the max dimension on the given axis.
/// Does NOT shrink beyond min.
pub fn shrink_max_to(&self, axis: Axis, dim: f64) -> Self {
match axis {
Axis::Horizontal => self.shrink_max_width_to(dim),
Axis::Vertical => self.shrink_max_height_to(dim),
}
}

/// Shrinks the max width to dim.
/// Does NOT shrink beyond min width.
pub fn shrink_max_width_to(&self, dim: f64) -> Self {
let mut max = self.max();
max.width = f64::max(dim, self.min().width);
BoxConstraints::new(self.min(), max)
}

/// Shrinks the max height to dim.
/// Does NOT shrink beyond min height.
pub fn shrink_max_height_to(&self, dim: f64) -> Self {
let mut max = self.max();
max.height = f64::max(dim, self.min().height);
BoxConstraints::new(self.min(), max)
}
}

#[cfg(test)]
Expand Down
27 changes: 27 additions & 0 deletions druid/src/widget/aspect_ratio_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use crate::debug_state::DebugState;

use crate::widget::Axis;
use druid::widget::prelude::*;
use druid::Data;
use tracing::{instrument, warn};
Expand Down Expand Up @@ -171,4 +172,30 @@ impl<T: Data> Widget<T> for AspectRatioBox<T> {
..Default::default()
}
}

fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> f64 {
match axis {
Axis::Horizontal => {
if bc.is_height_bounded() {
bc.max().height * self.ratio
} else {
self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
}
}
Axis::Vertical => {
if bc.is_width_bounded() {
bc.max().width / self.ratio
} else {
self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
}
}
}
}
}
22 changes: 22 additions & 0 deletions druid/src/widget/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use super::BackgroundBrush;
use crate::debug_state::DebugState;
use crate::kurbo::RoundedRectRadii;
use crate::widget::prelude::*;
use crate::widget::Axis;
use crate::{Color, Data, KeyOrValue, Point, WidgetPod};
use tracing::{instrument, trace, trace_span};

Expand Down Expand Up @@ -242,4 +243,25 @@ impl<T: Data> Widget<T> for Container<T> {
..Default::default()
}
}

fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> f64 {
let container_width = match &self.border {
Some(border) => border.width.resolve(env),
None => 0.0,
};
let child_bc = bc.shrink((2.0 * container_width, 2.0 * container_width));
let child_size = self
.child
.widget_mut()
.compute_max_intrinsic(axis, ctx, &child_bc, data, env);
let border_width_on_both_sides = container_width * 2.;
child_size + border_width_on_both_sides
}
}
13 changes: 12 additions & 1 deletion druid/src/widget/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use crate::debug_state::DebugState;
use crate::widget::prelude::*;
use crate::widget::WidgetWrapper;
use crate::widget::{Axis, WidgetWrapper};

/// A trait for types that modify behaviour of a child widget.
///
Expand Down Expand Up @@ -142,6 +142,17 @@ impl<T, W: Widget<T>, C: Controller<T, W>> Widget<T> for ControllerHost<W, C> {
..Default::default()
}
}

fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> f64 {
self.widget.compute_max_intrinsic(axis, ctx, bc, data, env)
}
}

impl<W, C> WidgetWrapper for ControllerHost<W, C> {
Expand Down
104 changes: 104 additions & 0 deletions druid/src/widget/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

//! A widget that arranges its children in a one-dimensional array.
use std::ops::Add;

use crate::debug_state::DebugState;
use crate::kurbo::{common::FloatExt, Vec2};
use crate::widget::prelude::*;
Expand Down Expand Up @@ -942,6 +944,108 @@ impl<T: Data> Widget<T> for Flex<T> {
..Default::default()
}
}

fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> f64 {
if self.direction != axis {
// Direction axis and sizing axis are different.
// We compute max(child dim in cross axis).
let mut max_size_on_cross_axis: f64 = 0.;
let mut available_size_on_main_axis = self.direction.major(bc.max());
let mut total_flex = 0.;
for child in self.children.iter_mut() {
match child {
Child::Fixed { widget, .. } => {
let new_bc = bc
.unbound_max(axis)
.shrink_max_to(self.direction, available_size_on_main_axis);
let size_on_main_axis = widget.widget_mut().compute_max_intrinsic(
self.direction,
ctx,
&new_bc,
data,
env,
);
let new_bc = new_bc.shrink_max_to(self.direction, size_on_main_axis);
let size_on_cross_axis = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, &new_bc, data, env);
available_size_on_main_axis -= size_on_main_axis;
max_size_on_cross_axis = max_size_on_cross_axis.max(size_on_cross_axis);
}
Child::FixedSpacer(kv, _) => {
let mut s = kv.resolve(env);
if s < 0.0 {
tracing::warn!("Length provided to fixed spacer was less than 0");
s = 0.;
}
max_size_on_cross_axis = max_size_on_cross_axis.max(s);
}
Child::Flex { flex, .. } | Child::FlexedSpacer(flex, _) => total_flex += *flex,
}
}
let space_per_flex = available_size_on_main_axis / total_flex;

if space_per_flex > 0.0 {
for child in self.children.iter_mut() {
// We ignore Child::FlexedSpacer because its cross size is irrelevant.
// Its flex matters only on main axis. But here we are interested in cross size of
// each flex child.
if let Child::Flex { widget, flex, .. } = child {
let main_axis_available_space = *flex * space_per_flex;
let new_bc = bc.shrink_max_to(axis, main_axis_available_space);
let size_on_cross_axis = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, &new_bc, data, env);
max_size_on_cross_axis = max_size_on_cross_axis.max(size_on_cross_axis);
}
}
}
max_size_on_cross_axis
} else {
// Direction axis and sizing axis are same.
// We compute total(child dim on that axis)
let mut total: f64 = 0.;
let mut max_flex_fraction: f64 = 0.;
let mut total_flex = 0.;
for child in self.children.iter_mut() {
match child {
Child::Fixed { widget, .. } => {
let s = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, bc, data, env);
total = total.add(s);
}
Child::Flex { widget, flex, .. } => {
let s = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, bc, data, env);
let flex_fraction = s / *flex;
total_flex += *flex;
max_flex_fraction = max_flex_fraction.max(flex_fraction);
}
Child::FixedSpacer(kv, _) => {
let mut s = kv.resolve(env);
if s < 0.0 {
tracing::warn!("Length provided to fixed spacer was less than 0");
s = 0.;
}
total = total.add(s);
}
Child::FlexedSpacer(flex, _) => {
total_flex += *flex;
}
}
}
total + max_flex_fraction * total_flex
}
}
}

impl CrossAxisAlignment {
Expand Down
81 changes: 81 additions & 0 deletions druid/src/widget/intrinsic_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::widget::Axis;
use crate::{
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Size,
UpdateCtx, Widget,
};

/// A widget that sizes its child to the child's maximum intrinsic width.
///
/// This widget is useful, for example, when unlimited width is available and you would like a child
/// that would otherwise attempt to expand infinitely to instead size itself to a more reasonable
/// width.
///
/// The constraints that this widget passes to its child will adhere to the parent's
/// constraints, so if the constraints are not large enough to satisfy the child's maximum intrinsic
/// width, then the child will get less width than it otherwise would. Likewise, if the minimum
/// width constraint is larger than the child's maximum intrinsic width, the child will be given
/// more width than it otherwise would.
pub struct IntrinsicWidth<T> {
child: Box<dyn Widget<T>>,
}

impl<T: Data> IntrinsicWidth<T> {
/// Wrap the given `child` in this widget.
pub fn new(child: impl Widget<T> + 'static) -> Self {
Self {
child: Box::new(child),
}
}
}

impl<T: Data> Widget<T> for IntrinsicWidth<T> {
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
self.child.event(ctx, event, data, env);
}

fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
self.child.lifecycle(ctx, event, data, env);
}

fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
self.child.update(ctx, old_data, data, env);
}

fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
let iw = self
.child
.compute_max_intrinsic(Axis::Horizontal, ctx, bc, data, env);
let new_bc = bc.shrink_max_width_to(iw);

self.child.layout(ctx, &new_bc, data, env)
}

fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
self.child.paint(ctx, data, env);
}

fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> f64 {
match axis {
Axis::Horizontal => self.child.compute_max_intrinsic(axis, ctx, bc, data, env),
Axis::Vertical => {
if !bc.is_width_bounded() {
let w = self
.child
.compute_max_intrinsic(Axis::Horizontal, ctx, bc, data, env);
let new_bc = bc.shrink_max_width_to(w);
self.child
.compute_max_intrinsic(axis, ctx, &new_bc, data, env)
} else {
self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
}
}
}
}
}
Loading

0 comments on commit 5bcf594

Please sign in to comment.