[material_ui] Add helper methods in gen_defaults template  (#12121)
diff --git a/packages/material_ui/tool/gen_defaults/templates/template.dart b/packages/material_ui/tool/gen_defaults/templates/template.dart
index d619a21..b196e8d 100644
--- a/packages/material_ui/tool/gen_defaults/templates/template.dart
+++ b/packages/material_ui/tool/gen_defaults/templates/template.dart
@@ -6,6 +6,9 @@
 
 import 'package:meta/meta.dart';
 
+import '../data/color_role.dart';
+import '../data/shape_struct.dart';
+
 enum _MaterialVersion { material3, material3Expressive }
 
 /// A template for generating Material 3 component defaults.
@@ -70,8 +73,8 @@
   /// The Material version this template is for.
   _MaterialVersion get _version;
 
-  /// The name of the class that will be generated (e.g. `_M3IconButtonDefaults`
-  /// or `_M3EIconButtonDefaults`).
+  /// The name of the class that will be generated (e.g. `_IconButtonDefaultsM3`
+  /// or `_IconButtonDefaultsM3E`).
   String get _className {
     assert(
       _nameRegExp.hasMatch(name),
@@ -79,8 +82,8 @@
     );
     final String camelName = name.replaceAll(' ', '');
     return switch (_version) {
-      _MaterialVersion.material3 => '_M3${camelName}Defaults',
-      _MaterialVersion.material3Expressive => '_M3E${camelName}Defaults',
+      _MaterialVersion.material3 => '_${camelName}DefaultsM3',
+      _MaterialVersion.material3Expressive => '_${camelName}DefaultsM3E',
     };
   }
 
@@ -93,6 +96,60 @@
   /// The [className] parameter must be used to declare the class.
   String generateContents(String className);
 
+  /// Generates a Dart number literal for token values.
+  String number(num value) => value.toString();
+
+  /// Generates a [ColorScheme] color expression for the given token.
+  String color(TokenColorRole role, String prefix) => '$prefix.${role.name}';
+
+  /// Generates a color expression with opacity applied.
+  String colorWithOpacity(TokenColorRole role, double opacity, String prefix) {
+    if (opacity == 1.0) {
+      return color(role, prefix);
+    }
+    return '${color(role, prefix)}.withOpacity(${number(opacity)})';
+  }
+
+  /// Generates an [OutlinedBorder] expression for a shape token.
+  ///
+  /// Currently supports:
+  ///   - `SHAPE_FAMILY_ROUNDED_CORNERS`, which maps to
+  ///     [RoundedRectangleBorder].
+  ///   - `SHAPE_FAMILY_CIRCULAR`, which maps to [StadiumBorder].
+  String shape(ShapeStruct shape, [String prefix = 'const ']) {
+    switch (shape.family) {
+      case 'SHAPE_FAMILY_ROUNDED_CORNERS':
+        final ShapeStruct(
+          :double topLeft,
+          :double topRight,
+          :double bottomLeft,
+          :double bottomRight,
+        ) = shape;
+        if (topLeft == topRight && topLeft == bottomLeft && topLeft == bottomRight) {
+          if (topLeft == 0) {
+            return '${prefix}RoundedRectangleBorder()';
+          }
+          return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(${number(topLeft)})))';
+        }
+        if (topLeft == topRight && bottomLeft == bottomRight) {
+          return '${prefix}RoundedRectangleBorder(borderRadius: BorderRadius.vertical('
+              '${topLeft > 0 ? 'top: Radius.circular(${number(topLeft)})' : ''}'
+              '${topLeft > 0 && bottomLeft > 0 ? ', ' : ''}'
+              '${bottomLeft > 0 ? 'bottom: Radius.circular(${number(bottomLeft)})' : ''}'
+              '))';
+        }
+        return '${prefix}RoundedRectangleBorder(borderRadius: '
+            'BorderRadius.only('
+            'topLeft: Radius.circular(${number(topLeft)}), '
+            'topRight: Radius.circular(${number(topRight)}), '
+            'bottomLeft: Radius.circular(${number(bottomLeft)}), '
+            'bottomRight: Radius.circular(${number(bottomRight)})))';
+      case 'SHAPE_FAMILY_CIRCULAR':
+        return '${prefix}StadiumBorder()';
+    }
+    throw UnsupportedError('Unsupported shape family type: ${shape.family}');
+  }
+
   /// Generates the file under the target path [materialLib] and formats it.
   void generateFile({bool verbose = false}) {
     final String snakeName = name.toLowerCase().replaceAll(' ', '_');
diff --git a/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart
index 11cef5f..b8676b9 100644
--- a/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart
+++ b/packages/material_ui/tool/gen_defaults/test/gen_defaults_test.dart
@@ -5,6 +5,8 @@
 import 'dart:io';
 
 import 'package:test/test.dart';
+import '../data/color_role.dart';
+import '../data/shape_struct.dart';
 import '../templates/template.dart';
 import 'test_fixtures/test_templates.dart';
 
@@ -66,6 +68,85 @@
       });
     }
 
+    test('color generates color expression', () {
+      final template = M3IconButtonTemplate(testPath());
+      expect(template.color(TokenColorRole.onSurface, '_colors'), '_colors.onSurface');
+    });
+
+    test('colorWithOpacity generates color expression with opacity', () {
+      final template = M3IconButtonTemplate(testPath());
+      expect(
+        template.colorWithOpacity(TokenColorRole.onSurface, 0.12, '_colors'),
+        '_colors.onSurface.withOpacity(0.12)',
+      );
+      expect(
+        template.colorWithOpacity(TokenColorRole.onSurface, 1.0, '_colors'),
+        '_colors.onSurface',
+      );
+    });
+
+    test('shape generates shape expressions', () {
+      final template = M3IconButtonTemplate(testPath());
+      expect(
+        template.shape(
+          const ShapeStruct(
+            family: 'SHAPE_FAMILY_ROUNDED_CORNERS',
+            topLeft: 8.0,
+            topRight: 8.0,
+            bottomLeft: 8.0,
+            bottomRight: 8.0,
+          ),
+        ),
+        'const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0)))',
+      );
+      expect(
+        template.shape(
+          const ShapeStruct(
+            family: 'SHAPE_FAMILY_ROUNDED_CORNERS',
+            topLeft: 8.0,
+            topRight: 8.0,
+            bottomLeft: 4.0,
+            bottomRight: 4.0,
+          ),
+        ),
+        'const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(8.0), bottom: Radius.circular(4.0)))',
+      );
+      expect(
+        template.shape(
+          const ShapeStruct(
+            family: 'SHAPE_FAMILY_CIRCULAR',
+            topLeft: 0.0,
+            topRight: 0.0,
+            bottomLeft: 0.0,
+            bottomRight: 0.0,
+          ),
+        ),
+        'const StadiumBorder()',
+      );
+    });
+
+    test('shape throws UnsupportedError for unsupported shape family', () {
+      final template = M3IconButtonTemplate(testPath());
+      expect(
+        () => template.shape(
+          const ShapeStruct(
+            family: 'SHAPE_FAMILY_UNKNOWN',
+            topLeft: 0.0,
+            topRight: 0.0,
+            bottomLeft: 0.0,
+            bottomRight: 0.0,
+          ),
+        ),
+        throwsA(
+          isA<UnsupportedError>().having(
+            (UnsupportedError e) => e.message,
+            'message',
+            'Unsupported shape family type: SHAPE_FAMILY_UNKNOWN',
+          ),
+        ),
+      );
+    });
+
     test('will run dart format over the generated file', () {
       final template = UnformattedTemplate(testPath());
       template.generateFile();
@@ -115,21 +196,33 @@
 ''';
 
 const _buttonExpressiveDefaultsClass = '''
-class _M3EIconButtonDefaults {
+class _IconButtonDefaultsM3E {
   static const double height = 40.0;
   static const double borderRadius = 8.0;
 }
 ''';
 
 const _buttonDefaultsClass = '''
-class _M3IconButtonDefaults {
+class _IconButtonDefaultsM3 {
+  _IconButtonDefaultsM3(this.context);
+
+  final BuildContext context;
+  late final ColorScheme _colors = Theme.of(context).colorScheme;
+
   static const double height = 40.0;
   static const double borderRadius = 8.0;
+  Color get iconColor => _colors.onSurfaceVariant;
+  Color get disabledIconColor => _colors.onSurface.withOpacity(0.38);
+  Color get hoveredStateLayerColor =>
+      _colors.onSurfaceVariant.withOpacity(0.08);
+  OutlinedBorder get shape => const RoundedRectangleBorder(
+    borderRadius: BorderRadius.all(Radius.circular(8.0)),
+  );
 }
 ''';
 
 const formattedClass = '''
-class _M3UnformattedDefaults {
+class _UnformattedDefaultsM3 {
   final int x = 1;
   final String y = 'hello';
 }
diff --git a/packages/material_ui/tool/gen_defaults/test/test_fixtures/icon_button_token_data.dart b/packages/material_ui/tool/gen_defaults/test/test_fixtures/icon_button_token_data.dart
index 8eb7fd0..d400b72 100644
--- a/packages/material_ui/tool/gen_defaults/test/test_fixtures/icon_button_token_data.dart
+++ b/packages/material_ui/tool/gen_defaults/test/test_fixtures/icon_button_token_data.dart
@@ -2,7 +2,22 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import '../../data/color_role.dart';
+import '../../data/shape_struct.dart';
+
 class TokenIconButton {
   static const double height = 40.0;
   static const double borderRadius = 8.0;
+  static const TokenColorRole iconColor = TokenColorRole.onSurfaceVariant;
+  static const TokenColorRole disabledIconColor = TokenColorRole.onSurface;
+  static const double disabledIconOpacity = 0.38;
+  static const TokenColorRole hoveredStateLayerColor = TokenColorRole.onSurfaceVariant;
+  static const double hoveredStateLayerOpacity = 0.08;
+  static const ShapeStruct pressedContainerShape = ShapeStruct(
+    family: 'SHAPE_FAMILY_ROUNDED_CORNERS',
+    topLeft: 8.00,
+    topRight: 8.00,
+    bottomLeft: 8.00,
+    bottomRight: 8.00,
+  );
 }
diff --git a/packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart b/packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart
index 01442bf..735382c 100644
--- a/packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart
+++ b/packages/material_ui/tool/gen_defaults/test/test_fixtures/test_templates.dart
@@ -48,8 +48,19 @@
   String generateContents(String className) {
     return '''
 class $className {
+  $className(this.context);
+
+  final BuildContext context;
+  late final ColorScheme _colors = Theme.of(context).colorScheme;
+
   static const double height = ${TokenIconButton.height};
   static const double borderRadius = ${TokenIconButton.borderRadius};
+  Color get iconColor => ${color(TokenIconButton.iconColor, '_colors')};
+  Color get disabledIconColor =>
+      ${colorWithOpacity(TokenIconButton.disabledIconColor, TokenIconButton.disabledIconOpacity, '_colors')};
+  Color get hoveredStateLayerColor =>
+      ${colorWithOpacity(TokenIconButton.hoveredStateLayerColor, TokenIconButton.hoveredStateLayerOpacity, '_colors')};
+  OutlinedBorder get shape => ${shape(TokenIconButton.pressedContainerShape)};
 }
 ''';
   }