buildWidget method
Compose Flutter widget with child widgets already built
Subclasses should override this method. This method provides a general description of the layout of this math node. The child nodes are built in prior. This method is only responsible for the placement of those child widgets accroding to the layout & other interactions.
Please ensure children works in the same order as updateChildren, computeChildOptions, and buildWidget.
Implementation
@override
BuildResult buildWidget(
MathOptions options, List<BuildResult?> childBuildResults) {
final flattenedBuildResults = childBuildResults
.expand((result) => result!.results ?? [result])
.toList(growable: false);
final flattenedChildOptions =
flattenedBuildResults.map((e) => e.options).toList(growable: false);
// assert(flattenedChildList.length == actualChildWidgets.length);
// We need to calculate spacings between nodes
// There are several caveats to consider
// - bin can only be bin, if it satisfies some conditions. Otherwise it will
// be seen as an ord
// - There could aligners and spacers. We need to calculate the spacing
// after filtering them out, hence the [traverseNonSpaceNodes]
final childSpacingConfs = List.generate(
flattenedChildList.length,
(index) {
final e = flattenedChildList[index];
return _NodeSpacingConf(
e.leftType, e.rightType, flattenedChildOptions[index], 0.0);
},
growable: false,
);
_traverseNonSpaceNodes(childSpacingConfs, (prev, curr) {
if (prev?.rightType == AtomType.bin &&
const {
AtomType.rel,
AtomType.close,
AtomType.punct,
null,
}.contains(curr?.leftType)) {
prev!.rightType = AtomType.ord;
if (prev.leftType == AtomType.bin) {
prev.leftType = AtomType.ord;
}
} else if (curr?.leftType == AtomType.bin &&
const {
AtomType.bin,
AtomType.open,
AtomType.rel,
AtomType.op,
AtomType.punct,
null
}.contains(prev?.rightType)) {
curr!.leftType = AtomType.ord;
if (curr.rightType == AtomType.bin) {
curr.rightType = AtomType.ord;
}
}
});
_traverseNonSpaceNodes(childSpacingConfs, (prev, curr) {
if (prev != null && curr != null) {
prev.spacingAfter = getSpacingSize(
prev.rightType,
curr.leftType,
curr.options.style,
).toLpUnder(curr.options);
}
});
_key = GlobalKey();
final lineChildren = List.generate(
flattenedBuildResults.length,
(index) => LineElement(
child: flattenedBuildResults[index].widget,
canBreakBefore: false, // TODO
alignerOrSpacer: flattenedChildList[index] is SpaceNode &&
(flattenedChildList[index] as SpaceNode).alignerOrSpacer,
trailingMargin: childSpacingConfs[index].spacingAfter,
),
growable: false,
);
final widget = Consumer<FlutterMathMode>(builder: (context, mode, child) {
if (mode == FlutterMathMode.view) {
return Line(
key: _key!,
children: lineChildren,
);
}
// Each EquationRow will filter out unrelated selection changes (changes
// happen entirely outside the range of this EquationRow)
return ProxyProvider<MathController, TextSelection>(
create: (_) => const TextSelection.collapsed(offset: -1),
update: (context, controller, _) {
final selection = controller.selection;
return selection.copyWith(
baseOffset:
selection.baseOffset.clampInt(range.start - 1, range.end + 1),
extentOffset:
selection.extentOffset.clampInt(range.start - 1, range.end + 1),
);
},
// Selector translates global cursor position to local caret index
// Will only update Line when selection range actually changes
child: Selector2<TextSelection, Tuple2<LayerLink, LayerLink>,
Tuple3<TextSelection, LayerLink?, LayerLink?>>(
selector: (context, selection, handleLayerLinks) {
final start = selection.start - this.pos;
final end = selection.end - this.pos;
final caretStart = caretPositions.slotFor(start).ceil();
final caretEnd = caretPositions.slotFor(end).floor();
final caretSelection = caretStart <= caretEnd
? selection.baseOffset <= selection.extentOffset
? TextSelection(
baseOffset: caretStart, extentOffset: caretEnd)
: TextSelection(
baseOffset: caretEnd, extentOffset: caretStart)
: const TextSelection.collapsed(offset: -1);
final startHandleLayerLink =
caretPositions.contains(start) ? handleLayerLinks.item1 : null;
final endHandleLayerLink =
caretPositions.contains(end) ? handleLayerLinks.item2 : null;
return Tuple3(
caretSelection,
startHandleLayerLink,
endHandleLayerLink,
);
},
builder: (context, conf, _) {
final value = Provider.of<SelectionStyle>(context);
return EditableLine(
key: _key,
children: lineChildren,
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
node: this,
preferredLineHeight: options.fontSize,
cursorBlinkOpacityController:
Provider.of<Wrapper<AnimationController>>(context).value,
selection: conf.item1,
startHandleLayerLink: conf.item2,
endHandleLayerLink: conf.item3,
cursorColor: value.cursorColor,
cursorOffset: value.cursorOffset,
cursorRadius: value.cursorRadius,
cursorWidth: value.cursorWidth,
cursorHeight: value.cursorHeight,
hintingColor: value.hintingColor,
paintCursorAboveText: value.paintCursorAboveText,
selectionColor: value.selectionColor,
showCursor: value.showCursor,
);
},
),
);
});
return BuildResult(
options: options,
italic: flattenedBuildResults.lastOrNull?.italic ?? 0.0,
skew: flattenedBuildResults.length == 1
? flattenedBuildResults.first.italic
: 0.0,
widget: widget,
);
}