voo_node_canvas 0.1.1 copy "voo_node_canvas: ^0.1.1" to clipboard
voo_node_canvas: ^0.1.1 copied to clipboard

A node-based canvas widget for creating visual node graphs with draggable nodes and connections

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/material.dart';

import 'package:voo_node_canvas/voo_node_canvas.dart';

void main() {
  runApp(const VooNodeCanvasExampleApp());
}

/// Example app demonstrating the VooNodeCanvas widget.
class VooNodeCanvasExampleApp extends StatelessWidget {
  /// Creates the example app.
  const VooNodeCanvasExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'VooNodeCanvas Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      themeMode: ThemeMode.system,
      home: const NodeCanvasExample(),
    );
  }
}

/// Main example screen showing the node canvas editor.
class NodeCanvasExample extends StatefulWidget {
  /// Creates the node canvas example.
  const NodeCanvasExample({super.key});

  @override
  State<NodeCanvasExample> createState() => _NodeCanvasExampleState();
}

class _NodeCanvasExampleState extends State<NodeCanvasExample> {
  late final CanvasController _controller;

  /// Node templates available in the palette.
  final List<NodeTemplate> _templates = [
    // Triggers category
    NodeTemplate(
      type: 'start',
      label: 'Start',
      description: 'Entry point of the flow',
      icon: Icons.play_circle_outline,
      color: Colors.green,
      category: 'Triggers',
      defaultSize: const Size(120, 80),
      defaultPorts: const [
        NodePort(id: 'out1', type: PortType.output),
      ],
    ),
    NodeTemplate(
      type: 'timer',
      label: 'Timer',
      description: 'Triggers on schedule',
      icon: Icons.timer_outlined,
      color: Colors.green,
      category: 'Triggers',
      defaultSize: const Size(120, 80),
      defaultPorts: const [
        NodePort(id: 'out1', type: PortType.output),
      ],
    ),

    // Processing category
    NodeTemplate(
      type: 'process',
      label: 'Process',
      description: 'Execute an action',
      icon: Icons.settings_outlined,
      color: Colors.blue,
      category: 'Processing',
      defaultSize: const Size(140, 100),
      defaultPorts: const [
        NodePort(id: 'in1', type: PortType.input),
        NodePort(id: 'out1', type: PortType.output),
      ],
    ),
    NodeTemplate(
      type: 'transform',
      label: 'Transform',
      description: 'Transform data',
      icon: Icons.transform,
      color: Colors.blue,
      category: 'Processing',
      defaultSize: const Size(140, 100),
      defaultPorts: const [
        NodePort(id: 'in1', type: PortType.input),
        NodePort(id: 'out1', type: PortType.output),
      ],
    ),
    NodeTemplate(
      type: 'filter',
      label: 'Filter',
      description: 'Filter items',
      icon: Icons.filter_alt_outlined,
      color: Colors.blue,
      category: 'Processing',
      defaultSize: const Size(140, 100),
      defaultPorts: const [
        NodePort(id: 'in1', type: PortType.input),
        NodePort(id: 'out1', type: PortType.output),
      ],
    ),

    // Logic category
    NodeTemplate(
      type: 'condition',
      label: 'Condition',
      description: 'Branch based on logic',
      icon: Icons.call_split,
      color: Colors.orange,
      category: 'Logic',
      defaultSize: const Size(140, 100),
      defaultPorts: const [
        NodePort(id: 'in1', type: PortType.input),
        NodePort(id: 'true', type: PortType.output, label: 'True'),
        NodePort(id: 'false', type: PortType.output, label: 'False'),
      ],
    ),
    NodeTemplate(
      type: 'merge',
      label: 'Merge',
      description: 'Combine inputs',
      icon: Icons.merge,
      color: Colors.orange,
      category: 'Logic',
      defaultSize: const Size(120, 100),
      defaultPorts: const [
        NodePort(id: 'in1', type: PortType.input),
        NodePort(id: 'in2', type: PortType.input),
        NodePort(id: 'out1', type: PortType.output),
      ],
    ),

    // Output category
    NodeTemplate(
      type: 'end',
      label: 'End',
      description: 'End of the flow',
      icon: Icons.stop_circle_outlined,
      color: Colors.red,
      category: 'Output',
      defaultSize: const Size(120, 80),
      defaultPorts: const [
        NodePort(id: 'in1', type: PortType.input),
      ],
    ),
    NodeTemplate(
      type: 'log',
      label: 'Log',
      description: 'Log output',
      icon: Icons.article_outlined,
      color: Colors.red,
      category: 'Output',
      defaultSize: const Size(120, 80),
      defaultPorts: const [
        NodePort(id: 'in1', type: PortType.input),
      ],
    ),
  ];

  @override
  void initState() {
    super.initState();
    _controller = CanvasController();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _handleSave() {
    final json = _controller.toJson();
    final jsonString = const JsonEncoder.withIndent('  ').convert(json);

    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Canvas JSON'),
        content: SingleChildScrollView(
          child: SelectableText(
            jsonString,
            style: const TextStyle(
              fontFamily: 'monospace',
              fontSize: 12,
            ),
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Close'),
          ),
        ],
      ),
    );
  }

  void _handleLoad() {
    // Sample JSON to load
    final sampleJson = {
      'nodes': [
        {
          'id': 'loaded_start',
          'position': {'dx': 50.0, 'dy': 150.0},
          'size': {'width': 120.0, 'height': 80.0},
          'ports': [
            {'id': 'out1', 'type': 'output'},
          ],
          'metadata': {'type': 'start'},
        },
        {
          'id': 'loaded_process',
          'position': {'dx': 250.0, 'dy': 130.0},
          'size': {'width': 140.0, 'height': 100.0},
          'ports': [
            {'id': 'in1', 'type': 'input'},
            {'id': 'out1', 'type': 'output'},
          ],
          'metadata': {'type': 'process'},
        },
        {
          'id': 'loaded_end',
          'position': {'dx': 480.0, 'dy': 150.0},
          'size': {'width': 120.0, 'height': 80.0},
          'ports': [
            {'id': 'in1', 'type': 'input'},
          ],
          'metadata': {'type': 'end'},
        },
      ],
      'connections': [
        {
          'id': 'conn1',
          'sourceNodeId': 'loaded_start',
          'sourcePortId': 'out1',
          'targetNodeId': 'loaded_process',
          'targetPortId': 'in1',
        },
        {
          'id': 'conn2',
          'sourceNodeId': 'loaded_process',
          'sourcePortId': 'out1',
          'targetNodeId': 'loaded_end',
          'targetPortId': 'in1',
        },
      ],
    };

    _controller.fromJson(
      sampleJson,
      nodeBuilder: _rebuildNodeWidget,
    );

    _showSnackBar('Loaded sample canvas');
  }

  void _handleClear() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Clear Canvas'),
        content: const Text('Are you sure you want to clear all nodes and connections?'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              _controller.clear();
              _showSnackBar('Canvas cleared');
            },
            child: const Text('Clear'),
          ),
        ],
      ),
    );
  }

  /// Rebuilds a node's widget content from its metadata.
  CanvasNode _rebuildNodeWidget(CanvasNode node) {
    final nodeType = node.metadata?['type'] as String?;
    if (nodeType == null) return node;

    // Find the matching template
    final template = _templates.where((t) => t.type == nodeType).firstOrNull;
    if (template == null) return node;

    return node.copyWith(
      child: _NodeContent(template: template),
    );
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('VooNodeCanvas Editor'),
        actions: [
          IconButton(
            icon: const Icon(Icons.download),
            tooltip: 'Load Sample',
            onPressed: _handleLoad,
          ),
          IconButton(
            icon: const Icon(Icons.save),
            tooltip: 'Export JSON',
            onPressed: _handleSave,
          ),
          IconButton(
            icon: const Icon(Icons.delete_forever),
            tooltip: 'Clear Canvas',
            onPressed: _handleClear,
          ),
        ],
      ),
      body: VooCanvasEditor(
        controller: _controller,
        templates: _templates,
        config: const CanvasConfig(
          gridSize: 20,
          showGrid: true,
          snapToGrid: false,
          minZoom: 0.25,
          maxZoom: 2.0,
        ),
        palettePosition: PalettePosition.left,
        paletteWidth: 220,
        showToolbar: true,
        toolbarPosition: ToolbarPosition.top,
        nodeBuilder: (template, node) => _NodeContent(template: template),
        onNodeTap: (node) {
          _showSnackBar('Tapped: ${node.id}');
        },
        onNodeCreated: (node, template) {
          _showSnackBar('Created: ${template.label}');
        },
        onConnectionCreated: (connection) {
          _showSnackBar(
            'Connected: ${connection.sourceNodeId} → ${connection.targetNodeId}',
          );
        },
        onConnectionTap: (connection) {
          _showSnackBar('Connection: ${connection.id}');
        },
      ),
    );
  }
}

/// Custom node content widget.
class _NodeContent extends StatelessWidget {
  const _NodeContent({required this.template});

  final NodeTemplate template;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final color = template.color ?? theme.colorScheme.primary;

    return Container(
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            color.withValues(alpha: 0.85),
            color.withValues(alpha: 0.65),
          ],
        ),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          if (template.icon != null)
            Icon(
              template.icon,
              color: Colors.white.withValues(alpha: 0.9),
              size: 24,
            ),
          const SizedBox(height: 4),
          Text(
            template.label,
            style: theme.textTheme.titleSmall?.copyWith(
              color: Colors.white,
              fontWeight: FontWeight.bold,
            ),
            textAlign: TextAlign.center,
          ),
          if (template.description != null)
            Padding(
              padding: const EdgeInsets.only(top: 2),
              child: Text(
                template.description!,
                style: theme.textTheme.bodySmall?.copyWith(
                  color: Colors.white.withValues(alpha: 0.7),
                  fontSize: 10,
                ),
                textAlign: TextAlign.center,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
              ),
            ),
        ],
      ),
    );
  }
}
1
likes
0
points
163
downloads

Publisher

verified publishervoostack.com

Weekly Downloads

A node-based canvas widget for creating visual node graphs with draggable nodes and connections

Homepage
Repository (GitHub)
View/report issues

Topics

#widget #canvas #node-graph #flutter-ui #drag-and-drop

Documentation

Documentation

License

unknown (license)

Dependencies

equatable, flutter, voo_responsive, voo_tokens

More

Packages that depend on voo_node_canvas