Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing intrinsics #2172

Merged
merged 9 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
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 druid::widget::Axis;
sjoshid marked this conversation as resolved.
Show resolved Hide resolved

/// 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
103 changes: 103 additions & 0 deletions druid/src/widget/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::debug_state::DebugState;
use crate::kurbo::{common::FloatExt, Vec2};
use crate::widget::prelude::*;
use crate::{Data, KeyOrValue, Point, Rect, WidgetPod};
use std::ops::Add;
sjoshid marked this conversation as resolved.
Show resolved Hide resolved
use tracing::{instrument, trace};

/// A container with either horizontal or vertical layout.
Expand Down Expand Up @@ -942,6 +943,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
83 changes: 83 additions & 0 deletions druid/src/widget/intrinsic_width.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use druid::Data;
sjoshid marked this conversation as resolved.
Show resolved Hide resolved

use crate::widget::Axis;
use crate::{
BoxConstraints, 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