Skip to content

Conversation

@LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented Mar 12, 2021

The safer part of Reland #63639.

The remaining part of #64615 is a bit riskier, with 31 internal test failures.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Mar 12, 2021
@google-cla google-cla bot added the cla: yes label Mar 12, 2021
/// * [RenderSizedOverflowBox], a render object that is a specific size but
/// passes its original constraints through to its child, which it allows to
/// overflow.
class RenderUnconstrainedBox extends RenderConstraintsTransformBox {
Copy link
Contributor Author

@LongCatIsLooong LongCatIsLooong Mar 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this class be deprecated? No one is using it internally and the superclass is more generalized.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be OK with deprecating this and redirecting people to RenderConstraintsTransformBox.

/// the console, and black and yellow striped areas will appear where the
/// overflow occurs.
///
/// This [RenderBox] allows the child to selectively enforce some of its
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by "selectively enforcing some of its intrinsic sizing rules"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reworded.


/// Renders a box, imposing no constraints on its child, allowing the child to
/// render at its "natural" size.
/// A [RenderBox] that applies an arbitrary transform to its [constraints]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worthwhile to document somewhere in here what this does if child is null.

constraints: constraints,
layoutChild: ChildLayoutHelper.layoutChild,
);
assert(constraintsTransform != null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert this in the constructor and setter instead of here?

/// * [RenderSizedOverflowBox], a render object that is a specific size but
/// passes its original constraints through to its child, which it allows to
/// overflow.
class RenderUnconstrainedBox extends RenderConstraintsTransformBox {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be OK with deprecating this and redirecting people to RenderConstraintsTransformBox.

///
/// The `alignment`, `clipBehavior` and `constraintsTransform` arguments must
/// not be null.
const ConstraintsTransformBox.unconstrained({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this just duplicating behavior of the UnconstrainedBox widget? Can we just link to that one in the docs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with the other two here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll expose the static functions instead of named constructors.

/// * [AlignmentDirectional] for [Directionality]-aware alignments.
final AlignmentGeometry alignment;

/// @{template flutter.widgets.constraintsTransform}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: @ goes inside the {

///
/// The function must return a [BoxConstraints] that is
/// [BoxConstraints.isNormalized].
/// @{endtemplate}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

///
/// The [debugTransformType] argument adds a debug label to this widget.
///
/// The `alignment`, `clipBehavior` and `constraintsTransform` arguments must
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the type String, presumably debugTransformType also cannot be null? Maybe assert that as well?

}

@override
RenderConstraintsTransformBox createRenderObject(BuildContext context) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also just be implemented as a stateless widget now which builds a ConstraintsTransformBox?

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a few questions and ideas as I try to understand this at a high level. I owe you a more decisive review.

required AlignmentGeometry alignment,
required TextDirection? textDirection,
Axis? constrainedAxis,
required BoxConstraintsTransform constraintsTransform,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an idea, let me know what you think:

Make this not required. If it's null, then it just uses the constraints it has as-is.

Maybe that's kind of pointless, but I was thinking it would allow you to get rid of _unmodified in the main PR: https://github.com/flutter/flutter/pull/64615/files#diff-f5f93c879cef9a102adbf148583de9f3d9a05f9678fdcad7bde106f62200bb34R2606

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll expose the static functions ('unconstrained' etc,.), instead of the named constructors then.

Comment on lines 2342 to 2345
/// ConstraintsTransformBox(
/// constraintsTransform: (BoxConstraints constraints) => constraints.copyWith(maxHeight: double.infinity),
/// child: const Card(child: Text('Hello World!')),
/// )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this also be achieved with the code below, or am I misunderstanding?

LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    return ConstrainedBox(
      constraints: constraints.copyWith(maxHeight: double.infinity),
      child: const Card(child: Text('Hello World!')),
    );
  },
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConstrainedBox is a bit different from ConstraintsTransformBox, as it does not replace the original constraints but adds additional constraints to the original constraints. In this example, if the parent is a SizedBox(height: 50) then in the above example the Card can be as high as it wants but with ConstrainedBox the maxium height is 50. I'll update the example to be more specific.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really thought that my example here was the same as ConstraintsTransformBox, but I tried it and they are not the same.

So here's a simple example not using anything new:

            Container(
              height: 100,
              color: Colors.red,
              clipBehavior: Clip.none,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: double.infinity),
                child: Container(
                  height: 200,
                  color: Colors.blue.withOpacity(0.5),
                ),
              ),
            ),

Both Containers are 100px high there. There is no overflow. I would have expected the blue Container to extend outside of the red Container, but I guess I'm wrong about how Flutter does this :)

Screen Shot 2021-03-16 at 12 10 06 PM

Now here's a similar thing using ConstraintsTransformBox:

            Container(
              height: 100,
              color: Colors.yellow.withOpacity(0.5),
              child: ConstraintsTransformBox(
                alignment:Alignment.topLeft,
                constraintsTransform: (BoxConstraints constraints) => constraints.copyWith(maxHeight: double.infinity),
                child: Container(
                  height: 200,
                  color: Colors.blue.withOpacity(0.5),
                ),
              ),
            ),

The inner blue Container is able to extend outside of its parent, with an overflow warning.

Screen Shot 2021-03-16 at 12 10 32 PM

So I think this makes sense and I understand how this widget produces a unique behavior that doesn't seem to be easily doable with existing widgets 👍

super(key: key, child: child);

/// Creates a widget that imposes no constraints on its child, allowing it to
/// render at its "natural" size. If the child overflows the parent's
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between "natural" as you use it here and "intrinsic" as in intrinsicHeight/Width?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also just out of curiosity, what if the child's intrinsic width or height is larger than the device's screen? Will it actually be able to render that big or will it be constrained by the screen size? (This would be larger than the parent in most normal cases, so it would also give an overflow warning, but I'm curious what would happen.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I'm using them interchangeably. I'll replace intrinsic with natrual.

I think the child will be rendered as if there was infinite space (given it's not clipped) and will be aligned per its alignment. But yeah if it ends up being larger than the RenderView you'll get a warning (this is a debugging widget in some sense).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this out and for this app:

    MaterialApp(
      home: Scaffold(
        body: ConstraintsTransformBox(
          constraintsTransform: myConstraintsTransform,
          child: Container(
            color: Colors.red,
          ),
        ),
      ),
    );

When myConstraintsTransform is...

  • (BoxConstraints constraints) => constraints, the whole screen is red.
  • (BoxConstraints constraints) => constraints.copyWith(maxHeight: 10000), the whole screen is red, and there is an overflow warning.
  • (BoxConstraints constraints) => constraints.copyWith(maxHeight: double.infinity), the whole screen is empty, no warning.

Should there be an error for that last case with infinite height? Or actually maybe that is the normal behavior when you make the constraints infinite in Flutter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Container has a LimitedBox which only kicks in when the size is unconstrained. I think what happened was when the maxHeight is unconstrained the LimitedBox will try to become as small as possible so the ColoredBox wrapped inside the LimitedBox is zero-sized (case 3).

@LongCatIsLooong LongCatIsLooong force-pushed the ConstraintsTransformBox branch from 2c21e64 to 27c7d1f Compare March 13, 2021 00:15
/// overflow.
///
@Deprecated(
'Use RenderConstraintsTransformBox instead. '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention the deprecation and what to use instead in the doc above?

/// When [child] is null, this widget becomes as small as possible and never
/// overflows
///
/// This widget can be used to ensure some of [child]'s intrinsic dimensions are
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intrinsic = natural, as used below?

/// overflows
///
/// This widget can be used to ensure some of [child]'s intrinsic dimensions are
/// honored, during development. For instance, if [child] requires a minimum
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "during development"? Should this widget not be used in production?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to "This widget can be used to ensure some of [child]'s natrual dimensions are honored, and get an early warning otherwise during development. "

///
/// The [ConstraintsTransformBox] becomes a proxy widget that has no effect on
/// layout if [constraintsTransform] is set to this.
static BoxConstraints unmodified(BoxConstraints constraints) => const BoxConstraints();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this return the argument "constraints"? Otherwise its not matching the documentation of being an identity function...


final String? debugTransformLabel = _debugTransformLabel.isNotEmpty
? _debugTransformLabel
: _knownTransforms[constraintsTransform];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: name this _debugKnownTransforms to message that it's only supposed to be used within debug* methods?

clipBehavior: clipBehavior,
);

static BoxConstraints _fail(BoxConstraints incoming) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kinda odd. How about defining UnconstrainedBox as a Stateless widget that builds a ConstraintsTransformBox as a child (composition over inheritance)?

/// positioning, and sizing widgets.
/// * [OverflowBox], a widget that imposes different constraints on its child
/// than it gets from its parent, possibly allowing the child to overflow
/// the parent.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also link to ConstraintsTransformBox here?

///
/// {@tool snippet}
/// In the following snippet, the [Card] is guaranteed to be at least as tall as
/// its "natrual" height. Unlike [UnconstrainedBox], it can become taller if its
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can -> will, it doesn't have a choice, right?

/// The text direction to use when interpreting the [alignment] if it is an
/// [AlignmentDirectional].
final TextDirection? textDirection;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep these white spaces between properties?

expect(
const UnconstrainedBox(constrainedAxis: Axis.vertical,).toString(),
equals('UnconstrainedBox(alignment: Alignment.center, constrainedAxis: vertical)'),

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

odd extra whitespace?

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

/// * [Alignment] for non-[Directionality]-aware alignments.
/// * [AlignmentDirectional] for [Directionality]-aware alignments.
final AlignmentGeometry alignment;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: keep this whitespace?

/// be retained, and if set to [Axis.horizontal], then horizontal constraints
/// will be retained.
final Axis? constrainedAxis;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: keep this whietspace?

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

I thought I already submitted another approval but I think I lost it somehow when I was posting comment1, comment2...

@fluttergithubbot
Copy link
Contributor

This pull request is not suitable for automatic merging in its current state.

  • The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@fluttergithubbot
Copy link
Contributor

This pull request is not suitable for automatic merging in its current state.

  • The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@fluttergithubbot fluttergithubbot merged commit 8c44abc into flutter:master Mar 19, 2021
@LongCatIsLooong LongCatIsLooong deleted the ConstraintsTransformBox branch March 19, 2021 23:00
mdebbar added a commit that referenced this pull request Mar 19, 2021
mdebbar added a commit that referenced this pull request Mar 19, 2021
@Hixie
Copy link
Contributor

Hixie commented Mar 20, 2021

Where can I read about the reason for the deprecations in this PR?

@LongCatIsLooong
Copy link
Contributor Author

@Hixie I thought by definition this is not a breaking change (no customer tests were broken) so doesn't warrant a design doc?

LongCatIsLooong added a commit to LongCatIsLooong/flutter that referenced this pull request Mar 20, 2021
@Hixie
Copy link
Contributor

Hixie commented Mar 20, 2021

Just curious why we deprecated it, not suggesting a design doc or anything. :-)

@LongCatIsLooong
Copy link
Contributor Author

@Hixie gotcha. With the PR the UnconstrainedBox widget will be wired to the new render object class so no framework widgets are using the deprecated render object now. The deprecated render object can be expressed in terms of the new one so it's deprecated.

@Hixie
Copy link
Contributor

Hixie commented Mar 20, 2021

Ah, cool.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants