Skip to content

Commit 5bcf594

Browse files
sjoshidxarvic
authored andcommitted
Add the compute_max_intrinsic method to the Widget trait (linebender#2172)
1 parent 70ab369 commit 5bcf594

12 files changed

+439
-3
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ You can find its changes [documented below](#070---2021-01-01).
7171
- `EventCtx::submit_notification_without_warning` ([#2141] by [@xarvic])
7272
- `WidgetPod::requested_layout` ([#2145] by [@xarvic])
7373
- Make `Parse` work better with floats and similar types ([#2148] by [@superfell])
74+
- Added `compute_max_intrinsic` method to the `Widget` trait, which determines the maximum useful dimension of the widget ([#2172] by [@sjoshid])
7475

7576
### Changed
7677

@@ -845,6 +846,7 @@ Last release without a changelog :(
845846
[#2151]: https://github.com/linebender/druid/pull/2151
846847
[#2157]: https://github.com/linebender/druid/pull/2157
847848
[#2158]: https://github.com/linebender/druid/pull/2158
849+
[#2172]: https://github.com/linebender/druid/pull/2172
848850

849851
[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
850852
[0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0

druid/src/box_constraints.rs

+48
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//! The fundamental druid types.
1616
1717
use crate::kurbo::Size;
18+
use crate::widget::Axis;
1819

1920
/// Constraints for layout.
2021
///
@@ -270,6 +271,53 @@ impl BoxConstraints {
270271
}
271272
}
272273
}
274+
275+
/// Sets the max on a given axis to infinity.
276+
pub fn unbound_max(&self, axis: Axis) -> Self {
277+
match axis {
278+
Axis::Horizontal => self.unbound_max_width(),
279+
Axis::Vertical => self.unbound_max_height(),
280+
}
281+
}
282+
283+
/// Sets max width to infinity.
284+
pub fn unbound_max_width(&self) -> Self {
285+
let mut max = self.max();
286+
max.width = f64::INFINITY;
287+
BoxConstraints::new(self.min(), max)
288+
}
289+
290+
/// Sets max height to infinity.
291+
pub fn unbound_max_height(&self) -> Self {
292+
let mut max = self.max();
293+
max.height = f64::INFINITY;
294+
BoxConstraints::new(self.min(), max)
295+
}
296+
297+
/// Shrinks the max dimension on the given axis.
298+
/// Does NOT shrink beyond min.
299+
pub fn shrink_max_to(&self, axis: Axis, dim: f64) -> Self {
300+
match axis {
301+
Axis::Horizontal => self.shrink_max_width_to(dim),
302+
Axis::Vertical => self.shrink_max_height_to(dim),
303+
}
304+
}
305+
306+
/// Shrinks the max width to dim.
307+
/// Does NOT shrink beyond min width.
308+
pub fn shrink_max_width_to(&self, dim: f64) -> Self {
309+
let mut max = self.max();
310+
max.width = f64::max(dim, self.min().width);
311+
BoxConstraints::new(self.min(), max)
312+
}
313+
314+
/// Shrinks the max height to dim.
315+
/// Does NOT shrink beyond min height.
316+
pub fn shrink_max_height_to(&self, dim: f64) -> Self {
317+
let mut max = self.max();
318+
max.height = f64::max(dim, self.min().height);
319+
BoxConstraints::new(self.min(), max)
320+
}
273321
}
274322

275323
#[cfg(test)]

druid/src/widget/aspect_ratio_box.rs

+27
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
use crate::debug_state::DebugState;
1616

17+
use crate::widget::Axis;
1718
use druid::widget::prelude::*;
1819
use druid::Data;
1920
use tracing::{instrument, warn};
@@ -171,4 +172,30 @@ impl<T: Data> Widget<T> for AspectRatioBox<T> {
171172
..Default::default()
172173
}
173174
}
175+
176+
fn compute_max_intrinsic(
177+
&mut self,
178+
axis: Axis,
179+
ctx: &mut LayoutCtx,
180+
bc: &BoxConstraints,
181+
data: &T,
182+
env: &Env,
183+
) -> f64 {
184+
match axis {
185+
Axis::Horizontal => {
186+
if bc.is_height_bounded() {
187+
bc.max().height * self.ratio
188+
} else {
189+
self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
190+
}
191+
}
192+
Axis::Vertical => {
193+
if bc.is_width_bounded() {
194+
bc.max().width / self.ratio
195+
} else {
196+
self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
197+
}
198+
}
199+
}
200+
}
174201
}

druid/src/widget/container.rs

+22
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use super::BackgroundBrush;
1818
use crate::debug_state::DebugState;
1919
use crate::kurbo::RoundedRectRadii;
2020
use crate::widget::prelude::*;
21+
use crate::widget::Axis;
2122
use crate::{Color, Data, KeyOrValue, Point, WidgetPod};
2223
use tracing::{instrument, trace, trace_span};
2324

@@ -242,4 +243,25 @@ impl<T: Data> Widget<T> for Container<T> {
242243
..Default::default()
243244
}
244245
}
246+
247+
fn compute_max_intrinsic(
248+
&mut self,
249+
axis: Axis,
250+
ctx: &mut LayoutCtx,
251+
bc: &BoxConstraints,
252+
data: &T,
253+
env: &Env,
254+
) -> f64 {
255+
let container_width = match &self.border {
256+
Some(border) => border.width.resolve(env),
257+
None => 0.0,
258+
};
259+
let child_bc = bc.shrink((2.0 * container_width, 2.0 * container_width));
260+
let child_size = self
261+
.child
262+
.widget_mut()
263+
.compute_max_intrinsic(axis, ctx, &child_bc, data, env);
264+
let border_width_on_both_sides = container_width * 2.;
265+
child_size + border_width_on_both_sides
266+
}
245267
}

druid/src/widget/controller.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
use crate::debug_state::DebugState;
1818
use crate::widget::prelude::*;
19-
use crate::widget::WidgetWrapper;
19+
use crate::widget::{Axis, WidgetWrapper};
2020

2121
/// A trait for types that modify behaviour of a child widget.
2222
///
@@ -142,6 +142,17 @@ impl<T, W: Widget<T>, C: Controller<T, W>> Widget<T> for ControllerHost<W, C> {
142142
..Default::default()
143143
}
144144
}
145+
146+
fn compute_max_intrinsic(
147+
&mut self,
148+
axis: Axis,
149+
ctx: &mut LayoutCtx,
150+
bc: &BoxConstraints,
151+
data: &T,
152+
env: &Env,
153+
) -> f64 {
154+
self.widget.compute_max_intrinsic(axis, ctx, bc, data, env)
155+
}
145156
}
146157

147158
impl<W, C> WidgetWrapper for ControllerHost<W, C> {

druid/src/widget/flex.rs

+104
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
//! A widget that arranges its children in a one-dimensional array.
1616
17+
use std::ops::Add;
18+
1719
use crate::debug_state::DebugState;
1820
use crate::kurbo::{common::FloatExt, Vec2};
1921
use crate::widget::prelude::*;
@@ -942,6 +944,108 @@ impl<T: Data> Widget<T> for Flex<T> {
942944
..Default::default()
943945
}
944946
}
947+
948+
fn compute_max_intrinsic(
949+
&mut self,
950+
axis: Axis,
951+
ctx: &mut LayoutCtx,
952+
bc: &BoxConstraints,
953+
data: &T,
954+
env: &Env,
955+
) -> f64 {
956+
if self.direction != axis {
957+
// Direction axis and sizing axis are different.
958+
// We compute max(child dim in cross axis).
959+
let mut max_size_on_cross_axis: f64 = 0.;
960+
let mut available_size_on_main_axis = self.direction.major(bc.max());
961+
let mut total_flex = 0.;
962+
for child in self.children.iter_mut() {
963+
match child {
964+
Child::Fixed { widget, .. } => {
965+
let new_bc = bc
966+
.unbound_max(axis)
967+
.shrink_max_to(self.direction, available_size_on_main_axis);
968+
let size_on_main_axis = widget.widget_mut().compute_max_intrinsic(
969+
self.direction,
970+
ctx,
971+
&new_bc,
972+
data,
973+
env,
974+
);
975+
let new_bc = new_bc.shrink_max_to(self.direction, size_on_main_axis);
976+
let size_on_cross_axis = widget
977+
.widget_mut()
978+
.compute_max_intrinsic(axis, ctx, &new_bc, data, env);
979+
available_size_on_main_axis -= size_on_main_axis;
980+
max_size_on_cross_axis = max_size_on_cross_axis.max(size_on_cross_axis);
981+
}
982+
Child::FixedSpacer(kv, _) => {
983+
let mut s = kv.resolve(env);
984+
if s < 0.0 {
985+
tracing::warn!("Length provided to fixed spacer was less than 0");
986+
s = 0.;
987+
}
988+
max_size_on_cross_axis = max_size_on_cross_axis.max(s);
989+
}
990+
Child::Flex { flex, .. } | Child::FlexedSpacer(flex, _) => total_flex += *flex,
991+
}
992+
}
993+
let space_per_flex = available_size_on_main_axis / total_flex;
994+
995+
if space_per_flex > 0.0 {
996+
for child in self.children.iter_mut() {
997+
// We ignore Child::FlexedSpacer because its cross size is irrelevant.
998+
// Its flex matters only on main axis. But here we are interested in cross size of
999+
// each flex child.
1000+
if let Child::Flex { widget, flex, .. } = child {
1001+
let main_axis_available_space = *flex * space_per_flex;
1002+
let new_bc = bc.shrink_max_to(axis, main_axis_available_space);
1003+
let size_on_cross_axis = widget
1004+
.widget_mut()
1005+
.compute_max_intrinsic(axis, ctx, &new_bc, data, env);
1006+
max_size_on_cross_axis = max_size_on_cross_axis.max(size_on_cross_axis);
1007+
}
1008+
}
1009+
}
1010+
max_size_on_cross_axis
1011+
} else {
1012+
// Direction axis and sizing axis are same.
1013+
// We compute total(child dim on that axis)
1014+
let mut total: f64 = 0.;
1015+
let mut max_flex_fraction: f64 = 0.;
1016+
let mut total_flex = 0.;
1017+
for child in self.children.iter_mut() {
1018+
match child {
1019+
Child::Fixed { widget, .. } => {
1020+
let s = widget
1021+
.widget_mut()
1022+
.compute_max_intrinsic(axis, ctx, bc, data, env);
1023+
total = total.add(s);
1024+
}
1025+
Child::Flex { widget, flex, .. } => {
1026+
let s = widget
1027+
.widget_mut()
1028+
.compute_max_intrinsic(axis, ctx, bc, data, env);
1029+
let flex_fraction = s / *flex;
1030+
total_flex += *flex;
1031+
max_flex_fraction = max_flex_fraction.max(flex_fraction);
1032+
}
1033+
Child::FixedSpacer(kv, _) => {
1034+
let mut s = kv.resolve(env);
1035+
if s < 0.0 {
1036+
tracing::warn!("Length provided to fixed spacer was less than 0");
1037+
s = 0.;
1038+
}
1039+
total = total.add(s);
1040+
}
1041+
Child::FlexedSpacer(flex, _) => {
1042+
total_flex += *flex;
1043+
}
1044+
}
1045+
}
1046+
total + max_flex_fraction * total_flex
1047+
}
1048+
}
9451049
}
9461050

9471051
impl CrossAxisAlignment {

druid/src/widget/intrinsic_width.rs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
use crate::widget::Axis;
2+
use crate::{
3+
BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, PaintCtx, Size,
4+
UpdateCtx, Widget,
5+
};
6+
7+
/// A widget that sizes its child to the child's maximum intrinsic width.
8+
///
9+
/// This widget is useful, for example, when unlimited width is available and you would like a child
10+
/// that would otherwise attempt to expand infinitely to instead size itself to a more reasonable
11+
/// width.
12+
///
13+
/// The constraints that this widget passes to its child will adhere to the parent's
14+
/// constraints, so if the constraints are not large enough to satisfy the child's maximum intrinsic
15+
/// width, then the child will get less width than it otherwise would. Likewise, if the minimum
16+
/// width constraint is larger than the child's maximum intrinsic width, the child will be given
17+
/// more width than it otherwise would.
18+
pub struct IntrinsicWidth<T> {
19+
child: Box<dyn Widget<T>>,
20+
}
21+
22+
impl<T: Data> IntrinsicWidth<T> {
23+
/// Wrap the given `child` in this widget.
24+
pub fn new(child: impl Widget<T> + 'static) -> Self {
25+
Self {
26+
child: Box::new(child),
27+
}
28+
}
29+
}
30+
31+
impl<T: Data> Widget<T> for IntrinsicWidth<T> {
32+
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
33+
self.child.event(ctx, event, data, env);
34+
}
35+
36+
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
37+
self.child.lifecycle(ctx, event, data, env);
38+
}
39+
40+
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
41+
self.child.update(ctx, old_data, data, env);
42+
}
43+
44+
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
45+
let iw = self
46+
.child
47+
.compute_max_intrinsic(Axis::Horizontal, ctx, bc, data, env);
48+
let new_bc = bc.shrink_max_width_to(iw);
49+
50+
self.child.layout(ctx, &new_bc, data, env)
51+
}
52+
53+
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
54+
self.child.paint(ctx, data, env);
55+
}
56+
57+
fn compute_max_intrinsic(
58+
&mut self,
59+
axis: Axis,
60+
ctx: &mut LayoutCtx,
61+
bc: &BoxConstraints,
62+
data: &T,
63+
env: &Env,
64+
) -> f64 {
65+
match axis {
66+
Axis::Horizontal => self.child.compute_max_intrinsic(axis, ctx, bc, data, env),
67+
Axis::Vertical => {
68+
if !bc.is_width_bounded() {
69+
let w = self
70+
.child
71+
.compute_max_intrinsic(Axis::Horizontal, ctx, bc, data, env);
72+
let new_bc = bc.shrink_max_width_to(w);
73+
self.child
74+
.compute_max_intrinsic(axis, ctx, &new_bc, data, env)
75+
} else {
76+
self.child.compute_max_intrinsic(axis, ctx, bc, data, env)
77+
}
78+
}
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)