Procházet zdrojové kódy

卷积:每组的增删按钮贴着自己那组输入框

原来两组的按钮都在 intro 里,于是 X 和 Y 的按钮一起跑到最上面,
看着像"Y 的按钮管着 X 那组"。

外壳加了个 beforeField(在指定输入框前插一块自定义 widget),
卷积把 X 的按钮插在第一个 X 框前、Y 的插在第一个 Y 框前(下标 = X 行数×2)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
nonvioce před 3 týdny
rodič
revize
0260d705b3

+ 6 - 4
lib/tools/convolution_calculator.dart

@@ -95,10 +95,12 @@ class _ConvolutionCalculatorPageState extends State<ConvolutionCalculatorPage> {
   Widget build(BuildContext context) => ToolScaffold(
     title: '卷积分布列',
     fields: _fields,
-    intro: Column(
-      crossAxisAlignment: CrossAxisAlignment.start,
-      children: [_rowButtons('X', _x), _rowButtons('Y', _y)],
-    ),
+    // 增删按钮贴着**自己那一组**:X 的按钮在 X 那组上面,Y 的在 Y 那组上面。
+    // Y 第一个输入框的下标 = X 的行数 × 2(每行两个框)。
+    beforeField: <int, Widget>{
+      0: _rowButtons('X', _x),
+      _x.length * 2: _rowButtons('Y', _y),
+    },
     compute: (session) async {
       _validate('X', _x);
       _validate('Y', _y);

+ 15 - 1
lib/tools/tool_kit.dart

@@ -22,6 +22,7 @@ class ToolScaffold extends StatefulWidget {
     required this.fields,
     required this.compute,
     this.intro,
+    this.beforeField = const {},
     this.autoRun = false,
   });
 
@@ -41,6 +42,12 @@ class ToolScaffold extends StatefulWidget {
   /// 工具自己的说明(公式长什么样、参数什么含义),显示在输入框上方。
   final Widget? intro;
 
+  /// 在第 n 个输入框**前面**插一块自定义 widget(下标按 [fields] 算)。
+  ///
+  /// 卷积那种"一组输入框配一组增删按钮"用它 —— 按钮得贴着**自己那一组**,
+  /// 全塞进 intro 就会两组的按钮一起跑到最上面去。
+  final Map<int, Widget> beforeField;
+
   /// 计算。自己从 [fields] 里取文本、拼 Symja 表达式、调 [SymjaSession.compute]。
   ///
   /// 返回若干条输出:文字的可点复制,图表的不复制(图没什么可复制的)。
@@ -132,7 +139,14 @@ class _ToolScaffoldState extends State<ToolScaffold> {
       group = <ToolField>[];
     }
 
-    for (final f in widget.fields) {
+    for (var i = 0; i < widget.fields.length; i++) {
+      final f = widget.fields[i];
+      final before = widget.beforeField[i];
+      if (before != null) {
+        flush();
+        rows.add(before);
+        rows.add(const SizedBox(height: 8));
+      }
       if (!f.rowWithPrevious) flush();
       group.add(f);
     }

+ 16 - 0
test/widget_test.dart

@@ -677,6 +677,22 @@ void main() {
       );
     });
 
+    testWidgets('卷积:每组的增删按钮贴着自己那组', (tester) async {
+      await openTool(tester, '卷积分布列');
+
+      final buttons = find.widgetWithText(TextButton, '添加一行');
+      expect(buttons, findsNWidgets(2)); // X 一组、Y 一组
+      final xBtn = tester.getRect(buttons.first).center.dy;
+      final yBtn = tester.getRect(buttons.last).center.dy;
+      final xFirst = tester.getRect(find.text('X 取值 1')).center.dy;
+      final yFirst = tester.getRect(find.text('Y 取值 1')).center.dy;
+
+      // X 的按钮在 X 那组上面;Y 的按钮在 X 那组**下面**、Y 那组上面
+      expect(xBtn, lessThan(xFirst));
+      expect(yBtn, greaterThan(xFirst));
+      expect(yBtn, lessThan(yFirst));
+    });
+
     testWidgets('函数图像能同时画多条曲线', (tester) async {
       await openTool(tester, '函数图像绘制');
       await tester.tap(find.widgetWithText(OutlinedButton, '添加函数'));