// 编辑器工具栏 — 底部工具面板 // // 三段式布局: // - 工具选择行(画笔/选择/文字/贴纸/照片) // - 工具选项行(颜色/大小 — 根据当前工具动态变化) // - 操作行(撤销/重做/清除) // // 设计规范:触摸目标 ≥ 44px,圆角 22px (pill) import 'package:flutter/material.dart'; import '../../../core/constants/design_tokens.dart'; import '../../../core/theme/app_colors.dart'; import '../bloc/editor_bloc.dart'; /// 工具栏高度 const double _toolbarHeight = 160; /// 编辑器工具栏 class EditorToolbar extends StatelessWidget { final EditorState state; final ValueChanged onEvent; const EditorToolbar({ super.key, required this.state, required this.onEvent, }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return Container( height: _toolbarHeight, decoration: BoxDecoration( color: colorScheme.surface, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.08), blurRadius: 8, offset: const Offset(0, -2), ), ], borderRadius: const BorderRadius.vertical(top: Radius.circular(22)), ), child: Column( children: [ // 工具选择行 _buildToolRow(context, colorScheme), const Divider(height: 1), // 工具选项行(颜色/大小) _buildOptionsRow(context, colorScheme), const Divider(height: 1), // 操作行(撤销/重做/清除) _buildActionRow(context, colorScheme), ], ), ); } // ============================================================ // 工具选择行 // ============================================================ Widget _buildToolRow(BuildContext context, ColorScheme colorScheme) { return SizedBox( height: 52, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ _toolButton(context, EditorTool.pen, Icons.gesture_rounded, '钢笔'), _toolButton(context, EditorTool.pencil, Icons.edit_rounded, '铅笔'), _toolButton(context, EditorTool.marker, Icons.brush_rounded, '马克笔'), _toolButton(context, EditorTool.eraser, Icons.auto_fix_high_rounded, '橡皮'), _toolButton(context, EditorTool.select, Icons.near_me_rounded, '选择'), _toolButton(context, EditorTool.text, Icons.text_fields_rounded, '文字'), _toolButton(context, EditorTool.sticker, Icons.emoji_emotions_rounded, '贴纸'), _toolButton(context, EditorTool.image, Icons.add_photo_alternate_rounded, '照片'), ], ), ); } Widget _toolButton( BuildContext context, EditorTool tool, IconData icon, String label, ) { final isActive = state.activeTool == tool; final colorScheme = Theme.of(context).colorScheme; return SizedBox( width: 44, height: 44, child: IconButton( onPressed: () => onEvent(ToolChanged(tool)), icon: Icon(icon, size: 22), color: isActive ? colorScheme.primary : colorScheme.onSurface.withValues(alpha: 0.5), style: IconButton.styleFrom( backgroundColor: isActive ? colorScheme.primaryContainer.withValues(alpha: 0.3) : Colors.transparent, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), tooltip: label, ), ); } // ============================================================ // 工具选项行(颜色 + 大小) // ============================================================ static const _colors = [ '#2D2420', // 主文字 '#E07A5F', // 珊瑚 '#81B29A', // 鼠尾草绿 '#F2CC8F', // 暖金 '#D4A5A5', // 玫瑰粉 '#42A5F5', // 信息蓝 '#9C27B0', // 紫色 '#FFFFFF', // 白色 ]; static const _widths = [1.5, 3.0, 5.0, 8.0, 12.0]; Widget _buildOptionsRow(BuildContext context, ColorScheme colorScheme) { // 画笔模式:颜色 + 粗细 if (state.isDrawingMode) { return _buildBrushOptions(context, colorScheme); } // 文字工具提示 if (state.activeTool == EditorTool.text) { return const SizedBox( height: 44, child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.text_fields, size: 16), SizedBox(width: 8), Text('点击画布输入文字', style: TextStyle(fontSize: 13)), ], ), ), ); } // 贴纸工具提示 if (state.activeTool == EditorTool.sticker) { return const SizedBox( height: 44, child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.emoji_emotions_outlined, size: 16), SizedBox(width: 8), Text('选择一个贴纸放到日记上', style: TextStyle(fontSize: 13)), ], ), ), ); } // 图片工具提示 if (state.activeTool == EditorTool.image) { return const SizedBox( height: 44, child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add_photo_alternate_outlined, size: 16), SizedBox(width: 8), Text('选择照片添加到日记', style: TextStyle(fontSize: 13)), ], ), ), ); } // 选择工具 return const SizedBox( height: 44, child: Center(child: Text('选择元素或添加内容')), ); } /// 画笔模式选项 — 颜色 + 粗细 Widget _buildBrushOptions(BuildContext context, ColorScheme colorScheme) { return SizedBox( height: 44, child: Row( children: [ // 颜色选择 Expanded( child: ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: DesignTokens.spacing12), itemCount: _colors.length, separatorBuilder: (_, __) => const SizedBox(width: 6), itemBuilder: (context, index) { final color = _colors[index]; final isActive = state.brushColor == color; return GestureDetector( onTap: () => onEvent(BrushChanged( type: state.brushType, color: color, width: state.brushWidth, )), child: Container( width: 28, height: 28, decoration: BoxDecoration( color: _parseHexColor(color), shape: BoxShape.circle, border: isActive ? Border.all(color: colorScheme.primary, width: 2.5) : Border.all(color: Colors.grey.shade300, width: 1), ), child: color == '#FFFFFF' ? const Icon(Icons.check, size: 16, color: Colors.grey) : null, ), ); }, ), ), // 分隔线 Container(width: 1, height: 24, color: colorScheme.outline.withValues(alpha: 0.2)), // 笔刷大小 SizedBox( width: 160, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: _widths.map((w) { final isActive = (state.brushWidth - w).abs() < 0.5; return GestureDetector( onTap: () => onEvent(BrushChanged( type: state.brushType, color: state.brushColor, width: w, )), child: Container( width: 28, height: 28, alignment: Alignment.center, decoration: BoxDecoration( shape: BoxShape.circle, border: isActive ? Border.all(color: colorScheme.primary, width: 2) : null, ), child: Container( width: (w / 12 * 16 + 4).clamp(4, 20), height: (w / 12 * 16 + 4).clamp(4, 20), decoration: BoxDecoration( color: _parseHexColor(state.brushColor), shape: BoxShape.circle, ), ), ), ); }).toList(), ), ), ], ), ); } // ============================================================ // 操作行 // ============================================================ Widget _buildActionRow(BuildContext context, ColorScheme colorScheme) { return SizedBox( height: 44, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // 撤销 IconButton( onPressed: state.strokes.isNotEmpty ? () => onEvent(Undo()) : null, icon: const Icon(Icons.undo_rounded), tooltip: '撤销', ), // 重做 IconButton( onPressed: state.redoStack.isNotEmpty ? () => onEvent(Redo()) : null, icon: const Icon(Icons.redo_rounded), tooltip: '重做', ), // 清除 IconButton( onPressed: state.strokes.isNotEmpty || state.elements.isNotEmpty ? () => onEvent(ClearCanvas()) : null, icon: const Icon(Icons.delete_outline_rounded), tooltip: '清除', ), // 保存状态指示 if (state.isDirty) Padding( padding: const EdgeInsets.symmetric(horizontal: DesignTokens.spacing8), child: Text( '未保存', style: TextStyle( fontSize: 12, color: colorScheme.onSurface.withValues(alpha: 0.5), ), ), ) else if (state.lastSavedAt != null) Padding( padding: const EdgeInsets.symmetric(horizontal: DesignTokens.spacing8), child: Text( '已保存', style: TextStyle( fontSize: 12, color: AppColors.success, ), ), ), ], ), ); } // ============================================================ // 工具函数 // ============================================================ Color _parseHexColor(String hex) { final hexStr = hex.replaceFirst('#', ''); if (hexStr.length != 6) return const Color(0xFF2D2420); final value = int.tryParse(hexStr, radix: 16); if (value == null) return const Color(0xFF2D2420); return Color(0xFF000000 + value); } }