I’ve been facing a problem with my mind map app, it is more like a node editor, when I move/drag the nodes it updates instantly, but when I try to drag from the node output to create a connection, the custompaint line doesn’t show up until I pan the canvas.
this is the mind map class
@override
Widget build(BuildContext context) {
return BlocListener<NodeConnectionCubit, NodeConnectionState>(
listener: (context, state) {
if (state is NodeConnectionUpdatedState) {
setState(() {});
}
},
child: Container(
color: const Color.fromARGB(255, 26, 26, 23),
height: MediaQuery.sizeOf(context).height,
width: MediaQuery.sizeOf(context).width,
child: GestureDetector(
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onDoubleTap: _resetOffsetAndScale,
child: CustomPaint(
painter: !_staticBackground
? DynamicBackground(_offset)
: StaticBackground(),
child: BlocBuilder<NodeCubit, NodeState>(
builder: (context, state) {
if (state is NodeUpdatedState) {
return Stack(
children: [
Positioned(
top: 0,
left: 0,
child: Transform(
transform:
Matrix4.diagonal3Values(_scale, _scale, 1.0)
..translate(_offset.dx, _offset.dy),
child: DeferredPointerHandler(
child: Stack(
children: [
for (int index = 0;
index <
context
.read<NodeCubit>()
.nodeList
.length;
index++)
Center(
child: MyNode(
index: index,
),
),
Stack(
children: [
for (int index = 0;
index <
context
.watch<NodeConnectionCubit>()
.linkedNodes
.length;
index++)
CustomPaint(
painter: NodeLinkPainter(
Colors.teal.withOpacity(0.5),
context
.watch<NodeConnectionCubit>()
.linkedNodes[index],
),
child:
Container(), // This is crucial for CustomPaint to take effect
),
],
)
],
),
),
),
),
// Toolbar and Add button remain outside of Transform.translate
ToolBar(context),
AddButton(
nodeTap: () {
setState(() {
context.read<NodeCubit>().addNode(
const Uuid().v4(),
null,
null,
null,
null,
null,
null,
context.read<NodeCubit>().nodeTypes.first,
false,
);
print(context.read<NodeCubit>().nodeList);
});
},
),
],
);
} else {
return const Text(
'Something went wrong with NodeUpdatedState');
}
},
),
),
),
),
);
}...etc
and this the custompaint in question
class NodeLinkPainter extends CustomPainter {
final Color color;
final NodeConnection connection;
NodeLinkPainter(this.color, this.connection);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
if (connection.outputOffset != null && connection.inputOffset != null) {
canvas.drawLine(connection.outputOffset!, connection.inputOffset!, paint);
} else {
print(
'OutputOffset or InputOffset is null: ${connection.outputOffset}, ${connection.inputOffset}');
}
}
@override
bool shouldRepaint(covariant NodeLinkPainter oldDelegate) {
return oldDelegate.connection.outputOffset != connection.outputOffset ||
oldDelegate.connection.inputOffset != connection.inputOffset;
}
}
here is the cubit
class NodeConnectionCubit extends Cubit<NodeConnectionState> {
List<NodeConnection> linkedNodes = [];
NodeConnectionCubit() : super(NodeConnectionInitialState()) {
emit(NodeConnectionUpdatedState(linkedNodes));
}
void addConnection(String? outputId, Offset? outputOffset, String? inputId,
Offset? inputOffset) {
linkedNodes.add(NodeConnection(
inputId: inputId,
outputId: outputId,
inputOffset: inputOffset,
outputOffset: outputOffset,
connectionOn: true,
));
emit(NodeConnectionUpdatedState(List.from(linkedNodes)));
print(
'Added connection: outputId: $outputId, outputOffset: $outputOffset, inputId: $inputId, inputOffset: $inputOffset');
}
void updateConnection(String? outputId, Offset? outputOffset, String? inputId,
Offset? inputOffset) {
for (var connection in linkedNodes) {
if (connection.outputId == outputId) {
connection.outputOffset = outputOffset;
print(
'Updated outputOffset for connection with outputId $outputId: $outputOffset');
}
if (connection.inputId == inputId) {
connection.inputOffset = inputOffset;
print(
'Updated inputOffset for connection with inputId $inputId: $inputOffset');
}
}
emit(NodeConnectionUpdatedState(List.from(linkedNodes)));
print('Emitting NodeConnectionUpdatedState with linkedNodes: $linkedNodes');
}
}
and finally, this is the output gesture detector
//Node Output
GestureDetector(
onPanStart: (details) {
setState(() {
outputOffset = details.globalPosition;
inputOffset = details.globalPosition;
final existingConnections = connectionCubit
.linkedNodes
.where((connection) =>
connection.outputId == node.id)
.toList();
if (existingConnections.isNotEmpty) {
// Update existing connections
// ignore: unused_local_variable
for (var connection in existingConnections) {
connectionCubit.updateConnection(node.id,
outputOffset, null, inputOffset);
}
} else {
// Add new connection if none exist
connectionCubit.addConnection(
node.id, outputOffset, null, inputOffset);
}
print(
'this is the starting outputOffset ${nodeList}');
});
},
onPanUpdate: (details) {
setState(() {
inputOffset = details.globalPosition;
context.read<NodeCubit>().updateOffset(
nodeList[widget.index!].id,
widgetOffset,
outputOffset,
inputOffset);
context
.read<NodeConnectionCubit>()
.updateConnection(node.id, outputOffset,
null, inputOffset);
print(
'this is the updated outputOffset ${connectionCubit.linkedNodes}');
});
},
onPanEnd: (details) {
setState(() {});
(() {
inputOffset = details.globalPosition;
context
.read<NodeConnectionCubit>()
.updateConnection(node.id, outputOffset,
null, inputOffset);
});
},
child: NodeInputOutput(
alignment: AlignmentDirectional.centerEnd,
nodeheight: node.height!.toDouble(),
nodeWidth: node.width!.toDouble() + 10,
dotHeight: 10,
dotWidth: 10,
offsetX: 0,
// offsetY: -24,
borderWidth: 0.5,
),
),
here is a short video about the problem
https://www.youtube.com/watch?v=5rNnerWsuh8
sorry about the quality, youtube compression is the culprit.
thank you for your help.