import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; import '../models/pen_tool.dart'; import '../models/pressure_curve.dart'; import '../utils/stroke_stabilizer.dart'; import '../widgets/ink_canvas.dart'; import 'color_preset_bar.dart'; /// Shared annotation toolbar used by note editor, PDF annotator, and PPT annotator. class AnnotationToolbar extends StatelessWidget { final PenTool currentTool; final Color currentColor; final double currentStrokeWidth; final bool filled; final PressureCurveType pressureCurveType; final StabilizationLevel stabilizationLevel; final bool canUndo; final bool canRedo; final ValueChanged onToolChanged; final ValueChanged onColorChanged; final ValueChanged onStrokeWidthChanged; final ValueChanged onFilledChanged; final ValueChanged onPressureCurveChanged; final ValueChanged onStabilizationChanged; final VoidCallback? onUndo; final VoidCallback? onRedo; final VoidCallback? onPreviousPage; final VoidCallback? onNextPage; final String? pageInfo; final InteractionMode interactionMode; final ValueChanged? onInteractionModeChanged; final double? zoomLevel; final VoidCallback? onZoomIn; final VoidCallback? onZoomOut; final VoidCallback? onZoomFitWidth; final String? zoomLabel; const AnnotationToolbar({ super.key, required this.currentTool, required this.currentColor, required this.currentStrokeWidth, this.filled = false, required this.pressureCurveType, required this.stabilizationLevel, required this.canUndo, required this.canRedo, required this.onToolChanged, required this.onColorChanged, required this.onStrokeWidthChanged, required this.onFilledChanged, required this.onPressureCurveChanged, required this.onStabilizationChanged, this.onUndo, this.onRedo, this.onPreviousPage, this.onNextPage, this.pageInfo, this.interactionMode = InteractionMode.draw, this.onInteractionModeChanged, this.zoomLevel, this.onZoomIn, this.onZoomOut, this.onZoomFitWidth, this.zoomLabel, }); static const _toolDefinitions = [ _ToolDef(PenTool.pen, Icons.edit, 'Pen'), _ToolDef(PenTool.marker, Icons.highlight, 'Marker'), _ToolDef(PenTool.highlighter, Icons.border_color, 'Highlighter'), _ToolDef(PenTool.eraser, Icons.auto_fix_normal, 'Eraser'), _ToolDef(PenTool.rectangle, Icons.rectangle_outlined, 'Rectangle'), _ToolDef(PenTool.ellipse, Icons.circle_outlined, 'Ellipse'), _ToolDef(PenTool.line, Icons.horizontal_rule, 'Line'), _ToolDef(PenTool.arrow, Icons.arrow_right_alt, 'Arrow'), _ToolDef(PenTool.text, Icons.text_fields, 'Text'), ]; bool get _isShapeTool { return currentTool == PenTool.rectangle || currentTool == PenTool.ellipse; } void _showColorPicker(BuildContext context) { showDialog( context: context, builder: (context) { Color pickerColor = currentColor; return AlertDialog( title: const Text('Pick a color'), content: SingleChildScrollView( child: ColorPicker( pickerColor: pickerColor, onColorChanged: (color) => pickerColor = color, ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), TextButton( onPressed: () { onColorChanged(pickerColor); Navigator.of(context).pop(); }, child: const Text('OK'), ), ], ); }, ); } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Row 1: Mode toggle + Tools + color presets + stroke width + undo/redo SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ // Pen/Navigate mode toggle if (onInteractionModeChanged != null) ...[ Tooltip( message: interactionMode == InteractionMode.draw ? 'Drawing mode' : 'Navigate mode', child: GestureDetector( onTap: () { final newMode = interactionMode == InteractionMode.draw ? InteractionMode.navigate : InteractionMode.draw; onInteractionModeChanged!(newMode); }, child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: interactionMode == InteractionMode.draw ? Theme.of(context).colorScheme.primaryContainer : Theme.of(context).colorScheme.tertiaryContainer, borderRadius: BorderRadius.circular(8), ), child: Icon( interactionMode == InteractionMode.draw ? Icons.edit : Icons.pan_tool, size: 20, color: interactionMode == InteractionMode.draw ? Theme.of(context).colorScheme.onPrimaryContainer : Theme.of( context, ).colorScheme.onTertiaryContainer, ), ), ), ), const SizedBox(width: 6), ], for (final def in _toolDefinitions) ...[ _ToolButton( icon: def.icon, label: def.label, isSelected: currentTool == def.tool, onPressed: () => onToolChanged(def.tool), ), const SizedBox(width: 4), ], // Filled toggle for shape tools if (_isShapeTool) ...[ const SizedBox(width: 4), Tooltip( message: filled ? 'Filled' : 'Outline', child: GestureDetector( onTap: () => onFilledChanged(!filled), child: Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: filled ? Theme.of(context).colorScheme.primaryContainer : Colors.transparent, borderRadius: BorderRadius.circular(4), border: Border.all( color: filled ? Theme.of(context).colorScheme.primary : Colors.grey.shade400, ), ), child: Icon( filled ? Icons.square : Icons.square_outlined, size: 16, color: filled ? Theme.of(context).colorScheme.onPrimaryContainer : Colors.grey.shade600, ), ), ), ), ], const SizedBox(width: 8), ColorPresetBar( selectedColor: currentColor, onColorSelected: onColorChanged, onOpenFullPicker: () => _showColorPicker(context), ), const SizedBox(width: 8), SizedBox( width: 120, child: Slider( value: currentStrokeWidth, min: 1.0, max: 20.0, divisions: 19, label: currentStrokeWidth.toStringAsFixed(1), onChanged: onStrokeWidthChanged, ), ), IconButton( icon: const Icon(Icons.undo), tooltip: 'Undo', iconSize: 20, padding: const EdgeInsets.all(4), onPressed: canUndo ? onUndo : null, ), IconButton( icon: const Icon(Icons.redo), tooltip: 'Redo', iconSize: 20, padding: const EdgeInsets.all(4), onPressed: canRedo ? onRedo : null, ), // Page navigation (optional, for PDF/PPT) if (onPreviousPage != null) ...[ IconButton( icon: const Icon(Icons.navigate_before), tooltip: 'Previous page', iconSize: 20, padding: const EdgeInsets.all(4), onPressed: onPreviousPage, ), if (pageInfo != null) Text(pageInfo!, style: const TextStyle(fontSize: 12)), IconButton( icon: const Icon(Icons.navigate_next), tooltip: 'Next page', iconSize: 20, padding: const EdgeInsets.all(4), onPressed: onNextPage, ), ], // Zoom controls (optional) if (onZoomIn != null) ...[ const SizedBox(width: 4), IconButton( icon: const Icon(Icons.zoom_out), tooltip: 'Zoom out', iconSize: 20, padding: const EdgeInsets.all(4), onPressed: onZoomOut, ), if (zoomLabel != null) Text(zoomLabel!, style: const TextStyle(fontSize: 11)), IconButton( icon: const Icon(Icons.zoom_in), tooltip: 'Zoom in', iconSize: 20, padding: const EdgeInsets.all(4), onPressed: onZoomIn, ), if (onZoomFitWidth != null) IconButton( icon: const Icon(Icons.fit_screen), tooltip: 'Fit to width', iconSize: 20, padding: const EdgeInsets.all(4), onPressed: onZoomFitWidth, ), ], ], ), ), // Row 2: Pressure curve + stabilization selectors Row( children: [ const Icon(Icons.touch_app, size: 14, color: Colors.grey), const SizedBox(width: 4), const Text( 'Pressure:', style: TextStyle(fontSize: 11, color: Colors.grey), ), const SizedBox(width: 4), _buildSegmentedButton( context: context, options: const { PressureCurveType.linear: 'Lin', PressureCurveType.soft: 'Soft', PressureCurveType.hard: 'Hard', }, selected: pressureCurveType, onChanged: onPressureCurveChanged, ), const SizedBox(width: 16), const Icon(Icons.gesture, size: 14, color: Colors.grey), const SizedBox(width: 4), const Text( 'Smooth:', style: TextStyle(fontSize: 11, color: Colors.grey), ), const SizedBox(width: 4), _buildSegmentedButton( context: context, options: const { StabilizationLevel.none: 'Off', StabilizationLevel.light: 'Low', StabilizationLevel.medium: 'Med', StabilizationLevel.heavy: 'High', }, selected: stabilizationLevel, onChanged: onStabilizationChanged, ), ], ), ], ), ); } Widget _buildSegmentedButton({ required BuildContext context, required Map options, required T selected, required ValueChanged onChanged, }) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), border: Border.all(color: Theme.of(context).colorScheme.outline), ), child: Row( mainAxisSize: MainAxisSize.min, children: options.entries.map((entry) { final isSelected = entry.key == selected; return GestureDetector( onTap: () => onChanged(entry.key), child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: isSelected ? Theme.of(context).colorScheme.primaryContainer : Colors.transparent, borderRadius: BorderRadius.circular(5), ), child: Text( entry.value, style: TextStyle( fontSize: 11, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, color: isSelected ? Theme.of(context).colorScheme.onPrimaryContainer : Theme.of(context).colorScheme.onSurface, ), ), ), ); }).toList(), ), ); } } class _ToolDef { final PenTool tool; final IconData icon; final String label; const _ToolDef(this.tool, this.icon, this.label); } class _ToolButton extends StatelessWidget { final IconData icon; final String label; final bool isSelected; final VoidCallback onPressed; const _ToolButton({ required this.icon, required this.label, required this.isSelected, required this.onPressed, }); @override Widget build(BuildContext context) { return Tooltip( message: label, child: Material( color: isSelected ? Theme.of(context).colorScheme.primaryContainer : Colors.transparent, borderRadius: BorderRadius.circular(8), child: InkWell( onTap: onPressed, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(6), child: Icon( icon, size: 20, color: isSelected ? Theme.of(context).colorScheme.onPrimaryContainer : Theme.of(context).colorScheme.onSurface, ), ), ), ), ); } }