Skip to content

Conversation

@bernaferrari
Copy link
Contributor

@bernaferrari bernaferrari commented Jul 26, 2022

Issue: #106362

This is radical, this is breaking, this is awesome. cc @gspencergoog

Edit: technically it is not breaking, I only break a single test (that is dumb and might or might not be fixed by another PR). But StrokeAlign change is really big.

Screen.Recording.2022-07-26.at.00.59.44.mov

Also, I made a completely new sample:

Click to see the sample code
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData.dark(),
      home: const Scaffold(body: HomePage()),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

enum Shapes {
  roundedRectangleBorder,
  roundedinside20,
  roundedcenter30,
  roundedOutside40,
  circleBorder,
  ovalBorder,
  stadiumBorder
}

class _HomePageState extends State<HomePage> {
  double sliderValue = 0;
  Shapes from = Shapes.roundedinside20;
  Shapes to = Shapes.roundedOutside40;

  OutlinedBorder getBorder(Shapes shape) {
    switch (shape) {
      case Shapes.roundedRectangleBorder:
        return RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20),
          side: BorderSide(
              color: Colors.green.withOpacity(0.5),
              width: 20,
              strokeAlign: StrokeAlign.center),
        );
      case Shapes.roundedinside20:
        return RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(5),
          side: BorderSide(
              color: Colors.purple.withOpacity(0.5),
              width: 20,
              strokeAlign: StrokeAlign.inside),
        );
      case Shapes.roundedcenter30:
        return RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
          side: BorderSide(
              color: Colors.blue.withOpacity(0.5),
              width: 30,
              strokeAlign: StrokeAlign.center),
        );
      case Shapes.roundedOutside40:
        return RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(15),
          side: BorderSide(
              color: Colors.red.withOpacity(0.5),
              width: 40,
              strokeAlign: StrokeAlign.outside),
        );
      case Shapes.circleBorder:
        return CircleBorder(
          side: BorderSide(
            color: Colors.green.withOpacity(0.5),
            width: 20,
            strokeAlign: StrokeAlign.center,
          ),
        );
      case Shapes.ovalBorder:
        return OvalBorder(
          side: BorderSide(
            color: Colors.yellow.withOpacity(0.5),
            width: 20,
            strokeAlign: StrokeAlign.center,
          ),
        );
      case Shapes.stadiumBorder:
        return StadiumBorder(
          side: BorderSide(
              color: Colors.yellow.withOpacity(0.5),
              width: 20,
              strokeAlign: StrokeAlign.center),
        );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Container(
          width: 400,
          height: 200,
          decoration: ShapeDecoration(
            shape: getBorder(from).lerpTo(getBorder(to), sliderValue) ??
                RoundedRectangleBorder(),
            color: Colors.white,
          ),
        ),
        const SizedBox(height: 40),
        Slider(
            // divisions: 10,
            value: sliderValue,
            onChanged: (value) {
              setState(() {
                sliderValue = value;
              });
            }),
        Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("From"),
            SizedBox(width: 10),
            DropdownButton<Shapes>(
                value: from,
                items: [
                  for (final shape in Shapes.values)
                    DropdownMenuItem(
                      value: shape,
                      child: Text(shape.toString()),
                    ),
                ],
                onChanged: (value) {
                  setState(() {
                    from = value!;
                  });
                }),
            SizedBox(width: 20),
            Text("To"),
            SizedBox(width: 10),
            DropdownButton<Shapes>(
                value: to,
                items: [
                  for (final shape in Shapes.values)
                    DropdownMenuItem(
                      value: shape,
                      child: Text(shape.toString()),
                    ),
                ],
                onChanged: (value) {
                  setState(() {
                    to = value!;
                  });
                }),
            SizedBox(width: 10),
            Text("${(sliderValue * 100).toStringAsFixed(0)}%"),
          ],
        ),
      ],
    );
  }
}

@flutter-dashboard flutter-dashboard bot added framework flutter/packages/flutter repository. See also f: labels. f: material design flutter/packages/flutter/material repository. labels Jul 26, 2022
@gspencergoog
Copy link
Contributor

This is great. It's not technically breaking if it doesn't break any tests in customer_testing or in Google internal tests. Since it's a new addition, it probably won't.

@bernaferrari
Copy link
Contributor Author

I just made some (many) changes based on your comments. I love the double strokeAlign, but I'm still not sure I want to get rid of StrokeAlign. Can't we have a global StrokeAlign.inside = 0.0? I think BorderSide.strokeAlignInside is too weird.

@gspencergoog
Copy link
Contributor

I don't think BorderSide.strokeAlignInside is that weird, we have lots of places where there are const defaults for double values, and it's a descriptive name.

And it seems like waaay overkill to have an entire class that effectively is a namespace for three doubles. It makes it more complicated to assign values to the strokeAlign value too:

BorderSide(strokeAlign: StrokeAlign(value));

vs

BorderSide(strokeAlign: value);

Seems more complicated for no benefit.

@bernaferrari
Copy link
Contributor Author

bernaferrari commented Jul 26, 2022

I simplified further, now: BorderSide(strokeAlign: StrokeAlign.inside) or BorderSide(strokeAlign: 0.5). I removed StrokeAlign(0.5). The opposite, strokeAlign: BorderSide.strokeAlignInside feels like a secret API.

But yeah, I might be able to remove it :( but will hurt.

@gspencergoog
Copy link
Contributor

One other thing to consider is that you might want to allow values outside of -1 and 1, so that you can animate the border with a bounce animation or other animation that goes slightly outside the bounds. The equations I gave you should mostly just work for that, although if people go too crazy, it'll result in some odd behavior (rects that get deflated to zero size, for instance).

@bernaferrari
Copy link
Contributor Author

The math for -1, 0, 1 is very hard to understand, but I think I got it working. Need to test if getInnerPath is working correctly later.

@bernaferrari
Copy link
Contributor Author

bernaferrari commented Jul 27, 2022

I thought about that. I think we should set loose the constraints and wait to see what shows up. Technically there isn't a difference from changing that to changing the width, it is just augmenting it.

But, if people want to have fun... committing.

@gspencergoog
Copy link
Contributor

gspencergoog commented Jul 27, 2022

Hey, @bernaferrari this looked like so much fun, I started playing with your code, and made my own branch with some changes. There's also some example code you might want to grab.

Here's my branch: https://github.com/gspencergoog/flutter/tree/stroke_align (and the diff if you'd rather look at that).

The sample looks like this when it runs:

ThrobWithBoundaries.mov
Throb.mov

Not intending it as a replacement for yours, just some ideas for you, feel free to appropriate any/all/none of it. Particularly useful, I think, were the new getters I added on BorderSide for strokeInset, strokeOutset, and strokeOffset.

@bernaferrari
Copy link
Contributor Author

bernaferrari commented Jul 27, 2022

I'll check soon (need to sleep), but congrats, that's awesome! I told you it is fun.. lol this is the most fun I've had in a while.

BTW, did you get any progress in the milter idea? I'm still wondering what that was, lol.

I still think you should rewrite ContinousRectangleBorder into something useful. It is the worst border by far and doesn't support any StrokeAlign 👀. Your Polygon code + this PR = a border everybody will love.

I opened the link and I'm already crying here. I wanted the squared border from Figma, you wanted the strokeAlign as double, I got the PR, you got the -1, 0, 1. Now we need to decide who is going to get the StrokeAlign and who is going to give up. lol
image

@bernaferrari
Copy link
Contributor Author

bernaferrari commented Jul 27, 2022

If you do this in your test, you will see some small weirdness:

static final Color borderColor = Colors.red.shade500.withOpacity(0.50);

image

@gspencergoog
Copy link
Contributor

If you do this in your test, you will see some small weirdness

Yes, perhaps it needs some more work.

@bernaferrari
Copy link
Contributor Author

bernaferrari commented Jul 27, 2022

That bug also happened in my branch, forget it. I think it is a bug in drawPath rounding almost-square to square (or something like this). I've seen it happening in other places 😢.

Ok, so fantastic work!

strokeInset and strokeOutset are exactly what I wanted when I made the similar but bad named lerpStrokeAlignDecreasing lol.

I'm merging all of your branch here (* except for something I'm still too connected to let it go, today 🤣).

Something I still think we need to fix is make this max 0. Negative EdgeInset isn't valid, right?
image

@bernaferrari
Copy link
Contributor Author

The Diagnosticable seems reasonably big. Do we expect a random number after BorderSide now?
image

@gspencergoog
Copy link
Contributor

The Diagnosticable seems reasonably big. Do we expect a random number after BorderSide now?

We use Diagnosticable on all the widgets, and many other classes, to handle formatting of the debug strings in a consistent way. The "random number" is part of the object hash, so that you can tell if two instances of the object are the same instance or not. It's not big in the runtime sense: it doesn't have any data members, and only costs something when toString is called, which is only used in debug builds anyhow.

@bernaferrari
Copy link
Contributor Author

bernaferrari commented Jul 27, 2022

Ok, I'll fix our tests, but I feel you will need to patch Google. That will probably be our breaking change.

BTW, how do we get the merge working? You told me we forgot in StrokeAlign. Should I take a mean of the StrokeAligns? Trying.

@bernaferrari
Copy link
Contributor Author

I don't know how to hash things, shouldn't the hash value be always the same?
Due to this, Diagnosticable result is random.
If I do Object.hash(1, 1) in a test and execute from terminal twice, the results are different.

@gspencergoog
Copy link
Contributor

The Object hash includes a component for the base object instance's location in memory, and since the object has a new instance each time, it appears random.

@bernaferrari
Copy link
Contributor Author

Is it possible to test that? Because there some tests that expect toString().

@gspencergoog
Copy link
Contributor

Yes, use expect(foo.toString(), equalsIgnoringHashCodes('Expected#00000()'));

@bernaferrari
Copy link
Contributor Author

How would that work here?
image

@gspencergoog
Copy link
Contributor

Ahh. It won't. OK, that's annoying. Instead, just add this to BorderSide:

  @override
  String toStringShort() => 'BorderSide';

@bernaferrari
Copy link
Contributor Author

Thanks. Aaaaalmost there!

So, I changed these to be the same default as BorderSide:
image
But now it produces BorderSide (instead of BorderSide()) and BorderSide(width: 0.0, style: none) (instead of style: BorderStyle.none. Any ideas on how to solve it?

Copy link
Contributor

@HansMuller HansMuller left a comment

Choose a reason for hiding this comment

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

LGTM

@gspencergoog gspencergoog added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 15, 2022
@auto-submit auto-submit bot merged commit dd8573b into flutter:master Aug 15, 2022
CaseyHillers added a commit that referenced this pull request Aug 16, 2022
@CaseyHillers
Copy link
Contributor

FYI this broke many tests, I've uploaded a revert in #109591. @gspencergoog and @HansMuller can you help with the reland to prevent these breakages?

@bernaferrari
Copy link
Contributor Author

How bad is it? Is it a golden, a bug from the code, the StrokeAlign or something else?

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 16, 2022
@HansMuller
Copy link
Contributor

This PR removed the StrokeAlign enum. That's a breaking change and it caused many internal test failures due to a StrokeAlign dependency (and see https://docs.flutter.dev/resources/compatibility). In this case a small G3Fix would probably enable this PR to land.

Failures reported in Google-internal issue b/242668688

@gspencergoog
Copy link
Contributor

OK, I'll take care of re-landing it.

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

Labels

autosubmit Merge PR when tree becomes green via auto submit App c: contributor-productivity Team-specific productivity, code health, technical debt. d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants