Implementation
Widget renderCanvasLayer(Size availableSize) {
return RepaintBoundary(
key: boundaryRepaintKey,
child: Container(
color: widget.theme.background,
child: Listener(
onPointerSignal: (pointerEvent) {
if (pointerEvent is PointerScrollEvent) {
applyZoomAtPoint(
pointerEvent.localPosition,
pointerEvent.scrollDelta.dy < 0 ? 1.15 : 0.87,
);
}
},
onPointerHover: (pointerEvent) =>
detectTooltipIntersection(pointerEvent.localPosition),
child: GestureDetector(
onScaleStart: (gestureDetails) {
if (isDrawingModeActive && gestureDetails.pointerCount == 1) {
final worldCoordinates = transformScreenToWorld(
gestureDetails.localFocalPoint.dx,
gestureDetails.localFocalPoint.dy,
);
setState(() => activeStroke = [worldCoordinates]);
} else {
previousFocalPoint = gestureDetails.localFocalPoint;
previousScaleGesture = 1.0;
}
},
onScaleUpdate: (gestureDetails) {
if (isDrawingModeActive &&
gestureDetails.pointerCount == 1 &&
activeStroke != null) {
final worldCoordinates = transformScreenToWorld(
gestureDetails.localFocalPoint.dx,
gestureDetails.localFocalPoint.dy,
);
setState(
() => activeStroke = [...activeStroke!, worldCoordinates],
);
} else {
setState(() {
if (gestureDetails.pointerCount >= 2 &&
previousScaleGesture != null) {
final dynamicFactor =
gestureDetails.scale / previousScaleGesture!;
currentOrigin = gestureDetails.localFocalPoint +
(currentOrigin - gestureDetails.localFocalPoint) *
dynamicFactor;
currentScale = (currentScale * dynamicFactor).clamp(
1.0,
3000.0,
);
previousScaleGesture = gestureDetails.scale;
} else if (previousFocalPoint != null) {
currentOrigin +=
gestureDetails.localFocalPoint - previousFocalPoint!;
}
previousFocalPoint = gestureDetails.localFocalPoint;
});
}
},
onScaleEnd: (gestureDetails) {
if (isDrawingModeActive &&
activeStroke != null &&
activeStroke!.length > 1) {
final finalizedStroke = FreehandItem(
worldPoints: List.from(activeStroke!),
color: widget.freehandColor,
strokeWidth: widget.freehandWidth,
);
setState(() {
recordedStrokes.add(finalizedStroke);
});
widget.onFreehandStroke?.call(List.from(activeStroke!));
}
setState(() => activeStroke = null);
previousFocalPoint = null;
previousScaleGesture = null;
},
onTapUp: (gestureDetails) {
detectTooltipIntersection(gestureDetails.localPosition);
if (widget.onTap != null) {
final worldCoordinates = transformScreenToWorld(
gestureDetails.localPosition.dx,
gestureDetails.localPosition.dy,
);
widget.onTap!(worldCoordinates.dx, worldCoordinates.dy);
}
},
onLongPressStart: (gestureDetails) {
if (widget.onLongPress != null) {
final worldCoordinates = transformScreenToWorld(
gestureDetails.localPosition.dx,
gestureDetails.localPosition.dy,
);
widget.onLongPress!(worldCoordinates.dx, worldCoordinates.dy);
}
},
child: Stack(
children: [
CustomPaint(
size: availableSize,
painter: CanvasRenderer(
origin: currentOrigin,
scale: currentScale,
items: widget.items,
freehandItems: recordedStrokes,
currentStroke: activeStroke,
showGrid: isGridVisible,
freehandColor: widget.freehandColor,
freehandWidth: widget.freehandWidth,
theme: widget.theme,
worldToScreen: transformWorldToScreen,
screenToWorld: transformScreenToWorld,
),
),
...widget.widgetItems.map((widgetItem) {
final mappedScreenPoint = transformWorldToScreen(
widgetItem.x,
widgetItem.y,
);
return Positioned(
left: mappedScreenPoint.dx -
50 * (widgetItem.alignment.x + 1) / 2,
top: mappedScreenPoint.dy -
50 * (widgetItem.alignment.y + 1) / 2,
child: widgetItem.child,
);
}),
if (widget.showLegend)
LegendWidget(items: widget.items, theme: widget.theme),
if (activeTooltip != null)
TooltipWidget(
tooltipData: activeTooltip!,
size: availableSize,
theme: widget.theme,
),
if (widget.showControls && widget.isFullScreen)
FloatingToolbar(
theme: widget.theme,
drawingMode: isDrawingModeActive,
gridVisible: isGridVisible,
isFullScreen: widget.isFullScreen,
onFitToContent: fitToContent,
onResetView: resetView,
onZoomIn: () => zoomFromCenter(1.3),
onZoomOut: () => zoomFromCenter(0.77),
onToggleGrid: () =>
setState(() => isGridVisible = !isGridVisible),
onToggleDrawing: () => setState(
() => isDrawingModeActive = !isDrawingModeActive,
),
onClearFreehand: clearFreehand,
onToggleFullScreen: toggleFullScreen,
),
if (widget.showControls &&
activeTooltip != null &&
widget.isFullScreen)
FloatingCoordinates(
tooltipData: activeTooltip!,
theme: widget.theme,
),
if (widget.showControls && !widget.isFullScreen)
Positioned(
top: 12,
right: 12,
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: BackdropFilter(
filter: ui.ImageFilter.blur(sigmaX: 12.0, sigmaY: 12.0),
child: Container(
decoration: BoxDecoration(
color: widget.theme.background.withOpacity(0.75),
border: Border.all(
color: widget.theme.axisColor.withOpacity(0.1),
width: 1,
),
),
child: ControlIconButton(
iconData: Icons.fullscreen_rounded,
tooltipText: 'Pantalla Completa',
onTapCallback: toggleFullScreen,
activeTheme: widget.theme,
),
),
),
),
),
],
),
),
),
),
);
}