2 Achegas dc0714647d ... d35b49ec3e

Autor SHA1 Mensaxe Data
  nonvioce d35b49ec3e i 与逗号换位置:逗号上到第 4 行,i 挪到最后一行 hai 4 semanas
  nonvioce f26a27c3ba /. 与 -> 合成一个 /.→ 键;加 i 键;窄字形点击区加最小宽度 hai 4 semanas
Modificáronse 3 ficheiros con 71 adicións e 10 borrados
  1. 13 3
      lib/calculator_page.dart
  2. 17 7
      lib/expr_editor.dart
  3. 41 0
      test/widget_test.dart

+ 13 - 3
lib/calculator_page.dart

@@ -238,6 +238,12 @@ class _CalculatorPageState extends State<CalculatorPage> {
         _doc.makeGroup();
         _doc.makeGroup();
       case _Act.power:
       case _Act.power:
         _doc.makePower();
         _doc.makePower();
+      case _Act.subst:
+        // 代入骨架:`/. x -> `,光标落在最后,接着打要代进去的值就行。
+        // 变量是 x 的占多数;要代 t/y 就点一下那个 x 改掉。
+        _doc.insert(LitNode('/. '));
+        _doc.insert(LitNode('x'));
+        _doc.insert(LitNode(' -> '));
       case _Act.brace:
       case _Act.brace:
         _doc.makeBrace();
         _doc.makeBrace();
       case _Act.frac:
       case _Act.frac:
@@ -466,8 +472,10 @@ class _CalculatorPageState extends State<CalculatorPage> {
           [
           [
             _Key('=', _Act.char, payload: '=', tint: true),
             _Key('=', _Act.char, payload: '=', tint: true),
             _Key('==', _Act.char, payload: '==', tint: true),
             _Key('==', _Act.char, payload: '==', tint: true),
-            _Key('/.', _Act.char, payload: '/.', tint: true),
-            _Key('->', _Act.char, payload: '->', tint: true),
+            // /. 和 -> 永远成对出现(代入就是 `式子 /. x -> 值`),
+            // 合成一个键直接给出骨架,省一个位置给逗号(矩阵行分隔)
+            _Key('/.→', _Act.subst, tint: true),
+            _Key(',', _Act.comma),
             _Key('{', _Act.brace, tint: true),
             _Key('{', _Act.brace, tint: true),
           ],
           ],
           // 5 数字与括号
           // 5 数字与括号
@@ -504,7 +512,8 @@ class _CalculatorPageState extends State<CalculatorPage> {
           [
           [
             _Key('⌫', _Act.back, tint: true),
             _Key('⌫', _Act.back, tint: true),
             _Key('CLR', _Act.clear, tint: true),
             _Key('CLR', _Act.clear, tint: true),
-            _Key(',', _Act.comma),
+            // 虚数单位。送进 Symja 的是 I,编辑框里显示小写 i
+            _Key('i', _Act.char, payload: 'I', tint: true, display: 'i'),
             _Key('ANS', _Act.char, payload: '%', tint: true, display: 'ANS'),
             _Key('ANS', _Act.char, payload: '%', tint: true, display: 'ANS'),
             // 键盘负责"敲字符",✓ 负责"执行"。两者都叫 = 会撞车。
             // 键盘负责"敲字符",✓ 负责"执行"。两者都叫 = 会撞车。
             _Key('✓', _Act.commit, primary: true),
             _Key('✓', _Act.commit, primary: true),
@@ -580,6 +589,7 @@ enum _Act {
   fn,
   fn,
   fnPrefixed,
   fnPrefixed,
   bigOp,
   bigOp,
+  subst,
   brace,
   brace,
   sqrt,
   sqrt,
   group,
   group,

+ 17 - 7
lib/expr_editor.dart

@@ -268,13 +268,23 @@ class _NodeView extends StatelessWidget {
     // 有显示名的(如 ANS)不加斜体 —— 它是缩写不是变量
     // 有显示名的(如 ANS)不加斜体 —— 它是缩写不是变量
     final isLetter =
     final isLetter =
         l.display == null && RegExp(r'^[A-Za-z]+$').hasMatch(l.text);
         l.display == null && RegExp(r'^[A-Za-z]+$').hasMatch(l.text);
-    return Text(
-      l.display ?? l.text,
-      style: TextStyle(
-        fontSize: size,
-        fontFamily: 'serif',
-        fontStyle: isLetter ? FontStyle.italic : FontStyle.normal,
-        color: scheme.onSurface,
+    // 最小宽度:`i`、`l`、`.` 这类窄字形,点击区只有字形那么宽(22 号字里
+    // `i` 才 5~6px),手指点不中 —— 用户实测反馈"i 没法被选中、光标没法移到
+    // i 附近"。给个字面量最小宽度,点中它才可靠。
+    //
+    // 为什么是 12 而不是更适合手指的 24/48:这个宽度是**布局**宽度,宽了就会
+    // 在字与字之间留下真空隙(跟 `Transform` 那种只改视觉不同)。衬线体 22 号
+    // 的数字约 11px 宽,12 只兜住真正窄的字形,`1 + 2` 这类常见写法不会被撑开。
+    return ConstrainedBox(
+      constraints: const BoxConstraints(minWidth: 12),
+      child: Text(
+        l.display ?? l.text,
+        style: TextStyle(
+          fontSize: size,
+          fontFamily: 'serif',
+          fontStyle: isLetter ? FontStyle.italic : FontStyle.normal,
+          color: scheme.onSurface,
+        ),
       ),
       ),
     );
     );
   }
   }

+ 41 - 0
test/widget_test.dart

@@ -237,6 +237,47 @@ void main() {
       expect(await lastExpr(tester), 'x*2+');
       expect(await lastExpr(tester), 'x*2+');
     });
     });
 
 
+    testWidgets('i 键:编辑框显示小写 i,送进引擎的是大写 I', (tester) async {
+      await pumpApp(tester);
+      await press(tester, 'i');
+
+      // 小写 i 在 Symja 里只是"叫 i 的未定义符号",虚数单位必须是大写 I
+      expect(await lastExpr(tester), 'I');
+      expect(
+        find.descendant(of: find.byType(ExprEditor), matching: find.text('i')),
+        findsOneWidget,
+      );
+    });
+
+    testWidgets('窄字形的点击区不会只剩字形那么宽(回归:i 点不中)', (tester) async {
+      await pumpApp(tester);
+      await press(tester, '2');
+      await press(tester, 'i');
+      expect(await lastExpr(tester), '2I');
+
+      final iRect = tester.getRect(
+        find.descendant(of: find.byType(ExprEditor), matching: find.text('i')),
+      );
+      // 以前就只有字形宽(5~6px),手指点不中;现在至少 12
+      expect(iRect.width, greaterThanOrEqualTo(12));
+
+      // 点左半边 → 光标落到 i 前面,接着打的 x 插在 2 和 i 之间
+      await tester.tapAt(Offset(iRect.left + 1, iRect.center.dy));
+      await tester.pumpAndSettle();
+      await press(tester, 'x');
+      expect(await lastExpr(tester), '2x*I');
+    });
+
+    testWidgets('/.→ 一个键给出代入骨架(原来 /. 和 -> 是两个键)', (tester) async {
+      await pumpApp(tester);
+      await press(tester, 'x');
+      await press(tester, '/.→');
+      await press(tester, '3');
+
+      // 骨架自带变量 x 和箭头,用户只要接着打要代进去的值
+      expect(await lastExpr(tester), 'x/. x -> 3');
+    });
+
     testWidgets('用新加的运算符键敲出一个可解的方程', (tester) async {
     testWidgets('用新加的运算符键敲出一个可解的方程', (tester) async {
       await pumpApp(tester);
       await pumpApp(tester);
       await press(tester, 'solve'); // Solve[□, x],光标在第一个参数
       await press(tester, 'solve'); // Solve[□, x],光标在第一个参数