JSON Canvas Viewer

Interatividade e Manipulação

Sistema de Gestos

stateDiagram-v2
    [*] --> Idle
    Idle --> Dragging: onPanStart
    Idle --> Resizing: onPanStart(handle)
    Dragging --> Dragging: onPanUpdate
    Resizing --> Resizing: onPanUpdate
    Dragging --> Idle: onPanEnd
    Resizing --> Idle: onPanEnd

Drag and Drop

Estado de Drag

class _JsonCanvasViewerState extends State<JsonCanvasViewer> {
  int? _draggedElementIndex;      // Elemento sendo arrastado
  Offset? _dragStartPosition;     // Posição inicial
  bool _isDragging = false;       // Flag de estado
}

Fluxo Completo

1. Inicialização

onPanStart

Salva índice e posição inicial

2. Movimento

onPanUpdate

Atualiza posição com compensação de escala

3. Notificação

onElementMoved

Callback para atualizar JSON

4. Finalização

onPanEnd

Limpa estado e flags

Atualização Durante Drag

void _onPanUpdate(DragUpdateDetails details, int elementIndex) {
  final delta = details.localPosition - _dragStartPosition!;
  final element = elements[elementIndex];
  
  // Compensa escala
  final scaleFactor = _getElementScaleFactor();
  final newX = currentX + (delta.dx / scaleFactor);
  final newY = currentY + (delta.dy / scaleFactor);
  
  // Atualiza elemento
  element['x'] = newX;
  element['y'] = newY;
  
  // Notifica callback
  Future.microtask(() {
    widget.onElementMoved?.call(elementIndex, newX, newY);
  });
}

Resize

Handles de Resize

top-left

Canto superior esquerdo

top-right

Canto superior direito

bottom-left

Canto inferior esquerdo

bottom-right

Canto inferior direito

Lógica de Resize

switch (_resizeHandle!) {
  case 'top-left':
    newWidth = currentWidth - deltaX;
    newHeight = currentHeight - deltaY;
    newX = currentX + deltaX;
    newY = currentY + deltaY;
    break;
  case 'bottom-right':
    newWidth = currentWidth + deltaX;
    newHeight = currentHeight + deltaY;
    break;
}

// Garante dimensões mínimas
newWidth = math.max(newWidth, 10.0);
newHeight = math.max(newHeight, 10.0);

Visual Feedback

Elemento Arrastado

Borda azul de 2px

Texto Arrastado

Background azul com 10% opacidade

Cursor

Grab durante hover, grabbing durante drag

Handles Visíveis

Aparecem apenas quando elemento selecionado


Atualização do JSON

Fluxo Bidirecional

sequenceDiagram
    participant U as Usuário
    participant JCV as JsonCanvasViewer
    participant CV as CanvasViewer
    participant HP as HomePage
    participant JE as JsonEditor

    U->>JCV: Arrasta elemento
    JCV->>JCV: Atualiza posição local
    JCV->>CV: onElementMoved(index, x, y)
    CV->>CV: Atualiza Map JSON
    CV->>HP: onJsonUpdated(updatedJson)
    HP->>HP: setState(_jsonData)
    HP->>JE: externalJsonData atualizado
    JE->>U: Editor exibe JSON atualizado

Callback para CanvasViewerWidget

void _updateJsonOnElementMove(
  int elementIndex, 
  double newX, 
  double newY, 
  {double? newWidth, double? newHeight}
) {
  final decoded = const JsonDecoder().convert(widget.jsonData);
  final elements = decoded['elements'] as List<dynamic>;
  
  if (elementIndex < elements.length) {
    final element = elements[elementIndex];
    element['x'] = newX;
    element['y'] = newY;
    
    if (newWidth != null) element['width'] = newWidth;
    if (newHeight != null) element['height'] = newHeight;
    
    const encoder = JsonEncoder.withIndent('  ');
    final updatedJson = encoder.convert(decoded);
    
    widget.onJsonUpdated?.call(updatedJson);
  }
}

Limitações

Elementos Não Redimensionáveis

text

Apenas drag (sem resize)

icon

Tamanho controlado por size

line

Largura/espessura fixas

Conversão de Centralização

Quando texto é arrastado, valores “center” são convertidos para números:

if (element['x'] is String) {
  element['x'] = newX;  // Converte "center" para número
}
element.remove('centerX');  // Remove flag

Próximos Passos

Renderização

Entenda o pipeline de renderização

Ver Pipeline →

Componentes

Veja a implementação dos componentes

Ver Componentes →

Arquitetura

Compreenda o fluxo de dados

Ver Arquitetura →