|
|
@@ -23,6 +23,8 @@ class ToolScaffold extends StatefulWidget {
|
|
|
required this.compute,
|
|
|
this.intro,
|
|
|
this.beforeField = const {},
|
|
|
+ this.runLabel = '计算',
|
|
|
+ this.runIcon,
|
|
|
this.autoRun = false,
|
|
|
});
|
|
|
|
|
|
@@ -42,6 +44,12 @@ class ToolScaffold extends StatefulWidget {
|
|
|
/// 工具自己的说明(公式长什么样、参数什么含义),显示在输入框上方。
|
|
|
final Widget? intro;
|
|
|
|
|
|
+ /// 主按钮的文字。绘图类工具写「绘制」比「计算」贴切。
|
|
|
+ final String runLabel;
|
|
|
+
|
|
|
+ /// 主按钮的图标,不给就按"计算"用算盘图标。
|
|
|
+ final IconData? runIcon;
|
|
|
+
|
|
|
/// 在第 n 个输入框**前面**插一块自定义 widget(下标按 [fields] 算)。
|
|
|
///
|
|
|
/// 卷积那种"一组输入框配一组增删按钮"用它 —— 按钮得贴着**自己那一组**,
|
|
|
@@ -190,8 +198,8 @@ class _ToolScaffoldState extends State<ToolScaffold> {
|
|
|
Expanded(
|
|
|
child: FilledButton.icon(
|
|
|
onPressed: _busy ? null : _run,
|
|
|
- icon: const Icon(Icons.calculate_outlined),
|
|
|
- label: const Text('计算'),
|
|
|
+ icon: Icon(widget.runIcon ?? Icons.calculate_outlined),
|
|
|
+ label: Text(widget.runLabel),
|
|
|
),
|
|
|
),
|
|
|
const SizedBox(width: 12),
|
|
|
@@ -256,6 +264,10 @@ class ToolField {
|
|
|
|
|
|
final String label;
|
|
|
final String hint;
|
|
|
+
|
|
|
+ /// 输入法键盘。**默认一律数字键盘**(带符号和小数点):工具页绝大多数输入
|
|
|
+ /// 是数,而输入法本身能切到字母 —— 表达式栏照样打得进 `Sin[x]`。
|
|
|
+ /// 这样每个框点下去的手感一致,不会有的弹数字、有的弹全键盘。
|
|
|
final TextInputType keyboard;
|
|
|
final TextEditingController controller;
|
|
|
|
|
|
@@ -472,6 +484,59 @@ Future<ToolOutput> numeric(
|
|
|
return ToolText(label, r.text, tex: null);
|
|
|
}
|
|
|
|
|
|
+/// 数值文本 → LaTeX:分数写成 `\frac{a}{b}`,其余原样。值都是 Symja 给的,这里只排版。
|
|
|
+String texNum(String v) {
|
|
|
+ final t = v.trim();
|
|
|
+ final m = RegExp(r'^(-?)(\d+)/(\d+)$').firstMatch(t);
|
|
|
+ if (m == null) return t;
|
|
|
+ return '${m[1]}\\frac{${m[2]}}{${m[3]}}';
|
|
|
+}
|
|
|
+
|
|
|
+/// 拼一条"… = 0"的等式(直线方程、圆的一般方程都用它)。
|
|
|
+///
|
|
|
+/// 每项是 `(系数, 变量部分的 LaTeX, 变量部分的纯文本)` —— 变量部分留空就是常数项;
|
|
|
+/// 系数是 Symja 算出来的**精确**值原文(`-2`、`1/2` …)。会自动处理:
|
|
|
+/// · 系数是 0 的项不写(圆的 F = 0 时那一项就不该出现)
|
|
|
+/// · 系数 ±1 又带变量时省略 1(写 `x` 而不是 `1·x`)
|
|
|
+/// · 首项不带正号,其余项按正负接 `+` / `-`
|
|
|
+({String plain, String tex}) polyEqualsZero(
|
|
|
+ List<(String coef, String varTex, String varPlain)> terms,
|
|
|
+) {
|
|
|
+ final plain = StringBuffer();
|
|
|
+ final tex = StringBuffer();
|
|
|
+ var first = true;
|
|
|
+
|
|
|
+ for (final (coef, varTex, varPlain) in terms) {
|
|
|
+ final t = coef.trim();
|
|
|
+ final n = asNumber(t);
|
|
|
+ if (n != null && n == 0) continue; // 系数为 0:整项不写
|
|
|
+
|
|
|
+ final neg = t.startsWith('-');
|
|
|
+ final mag = neg ? t.substring(1) : t;
|
|
|
+ // 系数是 ±1 又带变量时,1 不写
|
|
|
+ final hideOne = varPlain.isNotEmpty && mag == '1';
|
|
|
+
|
|
|
+ final sign = first ? (neg ? '-' : '') : (neg ? ' - ' : ' + ');
|
|
|
+ plain.write(sign);
|
|
|
+ tex.write(sign);
|
|
|
+
|
|
|
+ if (!hideOne) {
|
|
|
+ plain.write(varPlain.isEmpty ? mag : '$mag·');
|
|
|
+ tex.write(texNum(mag));
|
|
|
+ if (varPlain.isNotEmpty) tex.write(r' \cdot ');
|
|
|
+ }
|
|
|
+ plain.write(varPlain);
|
|
|
+ tex.write(varTex);
|
|
|
+ first = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (first) {
|
|
|
+ plain.write('0');
|
|
|
+ tex.write('0');
|
|
|
+ }
|
|
|
+ return (plain: '$plain = 0', tex: '$tex = 0');
|
|
|
+}
|
|
|
+
|
|
|
/// 把 Symja 输出的列表文本拆成嵌套的字符串列表。
|
|
|
///
|
|
|
/// 工具要拿到"一条条"结果(比如每个 k 的概率、每对卷积取值)才能分开显示,
|