renderCanvasLayer method

Widget renderCanvasLayer(
  1. Size availableSize
)

Implementation

Widget renderCanvasLayer(Size availableSize) {
  return RepaintBoundary(
      key: boundaryRepaintKey,
      child: Container(
        color: widget.theme.background,
        child: MouseRegion(
          onExit: (event) {
            if (mousePosition != null) {
              setState(() => mousePosition = null);
            }
          },
          child: Listener(
            onPointerSignal: (pointerEvent) {
              if (widget.isFullScreen && pointerEvent is PointerScrollEvent) {
                applyZoomAtPoint(
                  pointerEvent.localPosition,
                  pointerEvent.scrollDelta.dy < 0 ? 1.15 : 0.87,
                );
              }
            },
            onPointerHover: (pointerEvent) {
              detectTooltipIntersection(pointerEvent.localPosition);
              if (isEraserModeActive) {
                setState(() => mousePosition = pointerEvent.localPosition);
              }
            },
            onPointerDown: (pointerEvent) {
              if (pointerEvent.buttons == kSecondaryButton) {
                setState(() {
                  isDrawingModeActive = !isDrawingModeActive;
                  if (isDrawingModeActive) isEraserModeActive = false;
                });
              }
            },
            child: GestureDetector(
              onScaleStart: (gestureDetails) {
                if ((isDrawingModeActive || isEraserModeActive) &&
                    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 || isEraserModeActive) &&
                    gestureDetails.pointerCount == 1 &&
                    activeStroke != null) {
                  final worldCoordinates = transformScreenToWorld(
                    gestureDetails.localFocalPoint.dx,
                    gestureDetails.localFocalPoint.dy,
                  );
                  setState(() {
                    activeStroke = [...activeStroke!, worldCoordinates];
                    if (isEraserModeActive)
                      mousePosition = gestureDetails.localFocalPoint;
                  });
                } else {
                  setState(() {
                    if (gestureDetails.pointerCount >= 2 &&
                        previousScaleGesture != null) {
                      final dynamicFactor =
                          gestureDetails.scale / previousScaleGesture!;
                      final proposedScale = currentScale * dynamicFactor;
                      final clampedScale =
                          proposedScale.clamp(0.00001, 5000000.0);
                      final actualFactor = clampedScale / currentScale;
                      currentOrigin = gestureDetails.localFocalPoint +
                          (currentOrigin - gestureDetails.localFocalPoint) *
                              actualFactor;
                      currentScale = clampedScale;
                      previousScaleGesture = gestureDetails.scale;
                    } else if (previousFocalPoint != null) {
                      currentOrigin += gestureDetails.localFocalPoint -
                          previousFocalPoint!;
                    }
                    previousFocalPoint = gestureDetails.localFocalPoint;
                  });
                }
              },
              onScaleEnd: (gestureDetails) {
                if ((isDrawingModeActive || isEraserModeActive) &&
                    activeStroke != null &&
                    activeStroke!.length > 1) {
                  final finalizedStroke = FreehandItem(
                    worldPoints: List.from(activeStroke!),
                    color: widget.freehandColor,
                    strokeWidth: isEraserModeActive
                        ? currentEraserWidth
                        : widget.freehandWidth,
                    isEraser: isEraserModeActive,
                    baseScale: currentScale,
                  );
                  setState(() {
                    recordedStrokes.add(finalizedStroke);
                    undoneStrokes.clear();
                  });
                  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: List.from(recordedStrokes),
                      currentStroke: activeStroke,
                      currentIsEraser: isEraserModeActive,
                      showGrid: isGridVisible,
                      freehandColor: widget.freehandColor,
                      freehandWidth: widget.freehandWidth,
                      eraserWidth: currentEraserWidth,
                      mousePosition: mousePosition,
                      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,
                      eraserMode: isEraserModeActive,
                      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;
                        if (isDrawingModeActive) isEraserModeActive = false;
                      }),
                      onToggleEraser: () => setState(() {
                        isEraserModeActive = !isEraserModeActive;
                        if (isEraserModeActive) isDrawingModeActive = false;
                        if (!isEraserModeActive) mousePosition = null;
                      }),
                      onClearFreehand: clearFreehand,
                      onUndoFreehand: undoFreehand,
                      onRedoFreehand: redoFreehand,
                      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,
                            ),
                          ),
                        ),
                      ),
                    ),
                  if (isEraserModeActive)
                    Positioned(
                      bottom: 24,
                      left: 0,
                      right: 0,
                      child: Center(
                        child: ClipRRect(
                          borderRadius: BorderRadius.circular(16),
                          child: BackdropFilter(
                            filter: ui.ImageFilter.blur(
                                sigmaX: 12.0, sigmaY: 12.0),
                            child: Container(
                              padding: const EdgeInsets.symmetric(
                                  horizontal: 16, vertical: 8),
                              decoration: BoxDecoration(
                                color:
                                    widget.theme.background.withOpacity(0.75),
                                borderRadius: BorderRadius.circular(16),
                                border: Border.all(
                                  color:
                                      widget.theme.axisColor.withOpacity(0.1),
                                  width: 1,
                                ),
                                boxShadow: [
                                  BoxShadow(
                                    color: Colors.black.withOpacity(0.08),
                                    blurRadius: 16,
                                    offset: const Offset(0, 4),
                                  ),
                                ],
                              ),
                              child: Row(
                                mainAxisSize: MainAxisSize.min,
                                children: [
                                  Icon(Icons.cleaning_services_rounded,
                                      size: 16,
                                      color: widget.theme.axisColor
                                          .withOpacity(0.7)),
                                  const SizedBox(width: 8),
                                  SizedBox(
                                    width: 150,
                                    child: SliderTheme(
                                      data: const SliderThemeData(
                                        trackHeight: 2.0,
                                        thumbShape: RoundSliderThumbShape(
                                            enabledThumbRadius: 6.0),
                                        overlayShape: RoundSliderOverlayShape(
                                            overlayRadius: 14.0),
                                      ),
                                      child: Slider(
                                        value: currentEraserWidth,
                                        min: 5,
                                        max: 50,
                                        onChanged: (val) => setState(
                                            () => currentEraserWidth = val),
                                        activeColor: widget.theme.axisColor,
                                        inactiveColor: widget.theme.axisColor
                                            .withOpacity(0.2),
                                      ),
                                    ),
                                  ),
                                  const SizedBox(width: 8),
                                  SizedBox(
                                    width: 40,
                                    child: Text(
                                      '${currentEraserWidth.toInt()} px',
                                      style: TextStyle(
                                        fontSize: 12,
                                        fontWeight: FontWeight.w600,
                                        color: widget.theme.axisColor
                                            .withOpacity(0.8),
                                      ),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                ],
              ),
            ),
          ),
        ),
      ));
}