Getting ShapeBorders with custom paths to behave themselves?

So I don’t think any behaviour I’m looking for here is beyond what you’d get with, say, a RoundedRectangleBorder (in fact, on test 4 in the below example script, if I swap out my QuarticleBorder with a RRB directly, the image does in fact clip as expected). Yet getting that behaviour is eluding me.

On test 3, obviously I understand why this currently isn’t working as implemented, but to get the desired behaviour on tests 1, 2 and 5 (when the material this is decorating is sized by its child), this is the sensible way, right? When the widget is being sized by constraints, we don’t even need the margin at all, but as far as I know there’s no way of telling the Border that.

Am I in a rabbit hole of “this isn’t the right way to do this in the first place” here or something? Would appreciate any advice.

import 'package:flutter/material.dart';
import 'dart:math';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
// test 1: text inside a container
            Container(
              decoration: const ShapeDecoration(
                color: Colors.amber,
                shape: QuarticleBorder(),
              ),
              child: const Text('█Hello World█',
                  style: TextStyle(fontSize: 24, color: Colors.blue),
                  strutStyle: StrutStyle(
                    fontSize: 24,
                    forceStrutHeight: true,
                  )),
            ),
// result of test 1: Pass. The quarticle shape is correctly applied, though a strutstyle is needed to ensure the text widget itself doesn't add any padding. This is fine though, as the decoration can hardly be expected to know the size of the text.
            const SizedBox(height: 36),
// test 2: wrapping a sized container to confirm the inner path is correctly calculated
            Container(
              decoration: const ShapeDecoration(
                color: Colors.amber,
                shape: QuarticleBorder(),
              ),
              child: Container(height: 20, width: 20, color: Colors.blue),
            ),
// result of test 2: Pass. the inner path is correctly calculated.
            const SizedBox(height: 36),
// test 3: sizing the shape based on constraints rather than the child
            Container(
              color: Colors.red,
              width: 200,
              height: 12,
            ),
            const SizedBox(height: 36),
            SizedBox(
              width: 200,
              height: 200,
              child: Container(
                decoration: const ShapeDecoration(
                  color: Colors.amber,
                  shape: QuarticleBorder(),
                ),
              ),
            ),
// result of test 3: Fail. The margin calculation causes the shape to be larger than the the SizedBox it's in.
            const SizedBox(height: 36),
// test 4: does the quarticle shape clip an image?
            Container(
              width: 384,
              height: 216,
              decoration: const ShapeDecoration(
                image: DecorationImage(
                  image: AssetImage('assets/test_image.jpg'),
                  fit: BoxFit.fill,
                ),
                shape: QuarticleBorder(),
              ),
            ),
// result of test 4: Fail. The image is not clipped by the shape: we can't even see the quarticle shape.
            const SizedBox(height: 36),
// test 5: does the quarticle shape dynamically resize based on the child? (using a TextField to check this)
            Container(
              width: 200,
              decoration: const ShapeDecoration(
                color: Colors.amber,
                shape: QuarticleBorder(),
              ),
              child: EditableText(
                controller: TextEditingController(),
                focusNode: FocusNode(),
                style: const TextStyle(fontSize: 18, color: Colors.blue),
                cursorColor: Colors.blue,
                backgroundCursorColor: Colors.blue,
                strutStyle: const StrutStyle(
                  fontSize: 18,
                  forceStrutHeight: true,
                ),
                selectionColor: Colors.blue.withOpacity(0.5),
              ),
            ),
// result of test 5: Pass. The shape correctly resizes based on the child.
          ],
        ),
      ),
    );
  }
}

class QuarticleBorder extends ShapeBorder {
  const QuarticleBorder();

  double _calculateMargin(double size) {
    return size * ((pow(2, 1 / 4) - 1) / 2).toDouble();
  }

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.zero;

  @override
  Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
    return Path()..addRect(rect);
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
    final margin = _calculateMargin(rect.shortestSide);
    final outerRect = Rect.fromLTRB(
      rect.left - margin,
      rect.top - margin,
      rect.right + margin,
      rect.bottom + margin,
    );
    return QuarticlePath(outerRect).getPath();
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}

  @override
  ShapeBorder scale(double t) {
    return const QuarticleBorder();
  }
}

class QuarticlePath {
  final Rect rect;

  QuarticlePath(this.rect);

  Path getPath() {
    double cpDistParam = (4 / 3) * (pow(2, 3 / 4) - 1);

    double cornerFrame = min(rect.width, rect.height) / 2;
    double cpDist = cpDistParam * cornerFrame;

    double offset = rect.width - rect.height;
    String offsetDir = offset == 0
        ? "square"
        : offset > 0
            ? "horizontal"
            : "vertical";
    offset = offset.abs();

    Path path = Path();

    Offset lastPoint = Offset(rect.left + cornerFrame, rect.top);
    path.moveTo(lastPoint.dx, lastPoint.dy);

    void moveToNext(double x, double y, [bool move = false]) {
      if (move) {
        path.moveTo(x, y);
      } else {
        path.lineTo(x, y);
      }
      lastPoint = Offset(x, y);
    }

    void cubicToNext(Offset cp1, Offset cp2, Offset dest, bool move) {
      if (move) {
        path.moveTo(dest.dx, dest.dy);
      } else {
        path.cubicTo(cp1.dx, cp1.dy, cp2.dx, cp2.dy, dest.dx, dest.dy);
      }
      lastPoint = dest;
    }

    moveToNext(rect.left + cornerFrame, rect.top, true);
    if (offsetDir == "horizontal") {
      moveToNext(rect.left + cornerFrame + offset, rect.top);
    }
    cubicToNext(
      Offset(lastPoint.dx + cpDist, rect.top),
      Offset(rect.right, rect.top + cornerFrame - cpDist),
      Offset(rect.right, rect.top + cornerFrame),
      false,
    );

    if (offsetDir == "vertical") {
      moveToNext(rect.right, lastPoint.dy + offset);
    }
    cubicToNext(
      Offset(rect.right, lastPoint.dy + cpDist),
      Offset(rect.right - cornerFrame + cpDist, rect.bottom),
      Offset(rect.right - cornerFrame, rect.bottom),
      false,
    );

    if (offsetDir == "horizontal") {
      moveToNext(rect.left + cornerFrame, rect.bottom);
    }
    cubicToNext(
      Offset(rect.left + cornerFrame - cpDist, rect.bottom),
      Offset(rect.left, rect.bottom - cornerFrame + cpDist),
      Offset(rect.left, rect.bottom - cornerFrame),
      false,
    );

    if (offsetDir == "vertical") {
      moveToNext(rect.left, rect.top + cornerFrame);
    }
    cubicToNext(
      Offset(rect.left, rect.top + cornerFrame - cpDist),
      Offset(rect.left + cornerFrame - cpDist, rect.top),
      Offset(rect.left + cornerFrame, rect.top),
      false,
    );

    return path;
  }
}

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật