creek

The AI Image Editor of 2030
commit bae0639fc8dfe72c3ac8338b3d592b6aa71a4ff2
parent 7dcc2e700dfcb315c05de40f07c299d91c7da128
Author: ajcoder13 <avnijhalani@gmail.com>
Date:   Tue,  2 Dec 2025 06:47:54 +0530

Magic Draw functionality implemented, icons fixed, all bugs fixed

Diffstat:
Mlib/ui/pages/canvas_board_page.dart | 351++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mlib/ui/pages/canvas_toolbar/magic_draw_overlay.dart | 276++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
2 files changed, 442 insertions(+), 185 deletions(-)

diff --git a/lib/ui/pages/canvas_board_page.dart b/lib/ui/pages/canvas_board_page.dart @@ -109,6 +109,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { final FileService _fileService = FileService(); final ChangeStack _changeStack = ChangeStack(); final ImagePicker _picker = ImagePicker(); + final ChangeStack _magicDrawChangeStack = ChangeStack(); // --- STATE --- bool _hasUnsavedChanges = false; @@ -139,6 +140,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { bool _isMagicDrawActive = false; bool _isTextToolsActive = false; + bool _isMagicPanelDisabled = false; + bool _isViewMode = false; + // --- EDITING --- bool _isEditingText = false; final TextEditingController _textEditingController = TextEditingController(); @@ -175,7 +179,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { }); _hasUnsavedChanges = true; } - + // Note: injectedMedia handling is done inside _loadCanvasFromFile to ensure // it happens after file content is loaded, avoiding race conditions. } @@ -197,6 +201,33 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { }); } + Future<bool> _confirmDiscardMagicDraw() async { + if (_magicPaths.isEmpty) return true; // nothing drawn – no popup + + final result = await showDialog<bool>( + context: context, + builder: + (context) => AlertDialog( + title: const Text("Discard Magic Draw?"), + content: const Text( + "Leaving Magic Draw will remove your sketch. Continue?", + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text("Stay"), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: const Text("Discard"), + ), + ], + ), + ); + + return result == true; + } + Future<void> _analyzeCanvas() async { if (_isAnalyzing || _isInpainting) return; // Skip if busy setState(() => _isAnalyzing = true); @@ -217,7 +248,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { if (description != null && mounted) { setState(() { _aiDescription = description; - _isDescriptionExpanded = false; // Reset to collapsed on new description + _isDescriptionExpanded = + false; // Reset to collapsed on new description }); } } catch (e) { @@ -273,6 +305,17 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { } // --- UNDO/REDO --- + // --- MAGIC DRAW UNDO/REDO (NEW) --- + void _recordMagicChange(List<DrawingPath> oldMagicPaths) { + final newMagicPaths = List<DrawingPath>.from(_magicPaths); + _magicDrawChangeStack.add( + Change( + oldMagicPaths, + () => setState(() => _magicPaths = List.from(newMagicPaths)), // Redo + (val) => setState(() => _magicPaths = List.from(val)), // Undo + ), + ); + } void _recordChange(CanvasState oldState) { _resetInactivityTimer(); // Reset timer on undoable actions @@ -431,10 +474,10 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { if (_tempBaseImage != null) return; debugPrint("📸 [Magic Draw] Hiding strokes to capture clean base..."); - + // 1. Hide strokes by updating state setState(() => _isCapturingBase = true); - + // 2. Wait for frame to render await Future.delayed(const Duration(milliseconds: 50)); @@ -534,6 +577,18 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { // =========================================================================== // SAVE & LOAD LOGIC // =========================================================================== + Future<void> _handleMagicDrawExit() async { + // Use the helper method you already wrote: _confirmDiscardMagicDraw + if (_magicPaths.isNotEmpty && !_isInpainting) { + final confirm = await _confirmDiscardMagicDraw(); + if (confirm) { + _saveAndCloseMagicDraw(); + } + // If false, do nothing (stay) + } else { + _saveAndCloseMagicDraw(); + } + } void _handleBackNavigation() { // If in magic draw mode, just close tool first @@ -757,9 +812,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { } _hasUnsavedChanges = false; }); - + // Handle Injected Media (e.g. from Share) - if (widget.injectedMedia != null) { + if (widget.injectedMedia != null) { final oldState = _getCurrentState(); setState(() { elements.add({ @@ -840,7 +895,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { void _showComingSoon([dynamic feature]) => ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Coming soon'))); - + // [UPDATED] New Bottom Sheet for Assets void _openStylesheet() { showModalBottomSheet( @@ -939,7 +994,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { children: [ GestureDetector( onTap: () { - if (!_isMagicDrawActive) { + // <--- CHANGED: Allow deselecting if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON + if (!_isMagicDrawActive || _isMagicPanelDisabled) { _exitEditMode(); setState(() => selectedId = null); } @@ -951,10 +1007,10 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { boundaryMargin: const EdgeInsets.all(double.infinity), minScale: 0.01, maxScale: 10.0, - scaleEnabled: !_isMagicDrawActive, - panEnabled: !_isMagicDrawActive, + // <--- CHANGED: Enable Zoom/Pan if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON + scaleEnabled: !_isMagicDrawActive || _isMagicPanelDisabled, + panEnabled: !_isMagicDrawActive || _isMagicPanelDisabled, child: RepaintBoundary( - // WRAPPED CANVAS IN REPAINT BOUNDARY key: _canvasGlobalKey, child: SizedBox( width: _canvasSize.width, @@ -986,6 +1042,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { type: e['type'], content: e['content'], styleData: e, + // <--- OPTIONAL: If you want to move elements while Hand is active, change this line too: + // isSelected: isSelected && (!_isMagicDrawActive || _isMagicPanelDisabled), isSelected: isSelected && !_isMagicDrawActive, isEditing: isSelected && @@ -1004,7 +1062,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { _isTextToolsActive = true; }); setState(() { - // Bring to front elements.remove(e); elements.add(e); }); @@ -1040,7 +1097,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { }), // Drawing Layer IgnorePointer( - ignoring: !_isMagicDrawActive, + // <--- CHANGED: Ignore touches (disable drawing) if MagicDraw is OFF OR if Hand Mode (PanelDisabled) is ON + ignoring: + !_isMagicDrawActive || _isMagicPanelDisabled, child: RepaintBoundary( key: _drawingKey, child: GestureDetector( @@ -1054,10 +1113,12 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { size: Size.infinite, painter: CanvasPainter( paths: _paths, - // CONDITIONALLY HIDE MAGIC PATHS DURING CAPTURE - magicPaths: _isCapturingBase ? [] : _magicPaths, - // CONDITIONALLY HIDE CURRENT POINTS DURING CAPTURE - currentPoints: _isCapturingBase ? [] : _currentPoints, + magicPaths: + _isCapturingBase ? [] : _magicPaths, + currentPoints: + _isCapturingBase + ? [] + : _currentPoints, currentColor: _isEraser ? Colors.transparent @@ -1076,7 +1137,8 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ), ), - // AI Description Banner + // ... (Rest of your UI: AI Description, MagicDrawTools, etc.) ... + // I have truncated the bottom part as it remains unchanged. if (_aiDescription != null && !_isMagicDrawActive) Positioned( top: 10, @@ -1152,13 +1214,18 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { strokeWidth: _strokeWidth, isEraser: _isEraser, brandColors: _brandColors, - onClose: _saveAndCloseMagicDraw, + onClose: _handleMagicDrawExit, onColorChanged: (c) => setState(() => _selectedColor = c), onWidthChanged: (w) => setState(() => _strokeWidth = w), onEraserToggle: (e) => setState(() => _isEraser = e), - // CONNECTED CALLBACKS: onPromptSubmit: (prompt) => _processInpainting(prompt), isProcessing: _isInpainting, + onMagicPanelActivityToggle: + (disabled) => + setState(() => _isMagicPanelDisabled = disabled), + isMagicPanelDisabled: _isMagicPanelDisabled, + onViewModeToggle: + (enabled) => setState(() => _isViewMode = enabled), ), TextToolsOverlay( @@ -1177,7 +1244,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { }); }, onAddText: _addTextElement, - // onDelete: _deleteSelectedElement, // REMOVED onColorChanged: (c) => _updateSelectedTextProperty('style_color', c.value), @@ -1194,20 +1260,105 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { _isMagicDrawActive ? "Magic Draw" : (_isTextToolsActive ? "Text" : null), - onMagicDraw: - () => setState(() { - _isMagicDrawActive = !_isMagicDrawActive; - _isTextToolsActive = false; - _exitEditMode(); - _magicPaths.clear(); // Clear old magic paths - _tempBaseImage = null; // Force recapture next time - }), - onMedia: _pickImageFromGallery, - onStylesheet: _openStylesheet, - onTools: () => _showComingSoon('Tools'), - onText: _toggleTextTools, - onSelect: () => _showComingSoon('Select'), - onPlugins: () => _showComingSoon('Plugins'), + onMagicDraw: () async { + if (_isMagicDrawActive) { + final confirm = await _confirmDiscardMagicDraw(); + if (!confirm) return; + setState(() { + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + _isMagicDrawActive = false; + }); + return; + } + setState(() { + _isMagicDrawActive = true; + _isTextToolsActive = false; + _exitEditMode(); + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + }); + }, + onMedia: () async { + if (_isMagicDrawActive) { + final confirm = await _confirmDiscardMagicDraw(); + if (!confirm) return; + setState(() { + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + _isMagicDrawActive = false; + }); + } + _pickImageFromGallery(); + }, + onStylesheet: () async { + if (_isMagicDrawActive) { + final confirm = await _confirmDiscardMagicDraw(); + if (!confirm) return; + setState(() { + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + _isMagicDrawActive = false; + }); + } + _openStylesheet(); + }, + onTools: () async { + if (_isMagicDrawActive) { + final confirm = await _confirmDiscardMagicDraw(); + if (!confirm) return; + setState(() { + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + _isMagicDrawActive = false; + }); + } + _showComingSoon('Tools'); + }, + onText: () async { + if (_isMagicDrawActive) { + final confirm = await _confirmDiscardMagicDraw(); + if (!confirm) return; + setState(() { + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + _isMagicDrawActive = false; + }); + } + _toggleTextTools(); + }, + onSelect: () async { + if (_isMagicDrawActive) { + final confirm = await _confirmDiscardMagicDraw(); + if (!confirm) return; + setState(() { + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + _isMagicDrawActive = false; + }); + } + _showComingSoon('Select'); + }, + onPlugins: () async { + if (_isMagicDrawActive) { + final confirm = await _confirmDiscardMagicDraw(); + if (!confirm) return; + setState(() { + _magicPaths.clear(); + _magicDrawChangeStack.clear(); + _tempBaseImage = null; + _isMagicDrawActive = false; + }); + } + _showComingSoon('Plugins'); + }, ), ), ], @@ -1217,7 +1368,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ), ); } - // --- DRAWING HELPERS --- void _onPanUpdate(DragUpdateDetails details) { @@ -1233,6 +1383,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { if (_currentPoints.isNotEmpty) { if (_isMagicDrawActive) { // ADD TO MAGIC PATHS (Temporary mask) + final oldMagicPaths = List<DrawingPath>.from(_magicPaths); _magicPaths.add( DrawingPath( points: List.from(_currentPoints), @@ -1241,6 +1392,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { isEraser: _isEraser, ), ); + _recordMagicChange(oldMagicPaths); _currentPoints = []; } else { // NORMAL DRAWING @@ -1286,7 +1438,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { File? maskFile = await _generateMaskImageFromPaths( _magicPaths, _canvasSize, - _tempBaseImage, + _tempBaseImage, ); if (maskFile == null) throw Exception("Failed to generate mask"); @@ -1298,7 +1450,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ); _addGeneratedImage(newImageUrl); - } else { // --- SKETCH-TO-IMAGE FLOW (New) --- // Capture the entire canvas (strokes only since no images exist) @@ -1313,7 +1464,6 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { _addGeneratedImage(newImageUrl); } - } catch (e) { debugPrint("Generation Error: $e"); ScaffoldMessenger.of( @@ -1322,28 +1472,29 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { } finally { setState(() { _isInpainting = false; - _tempBaseImage = null; // Reset base image - _magicPaths.clear(); // Always clear magic paths on end + _tempBaseImage = null; + _magicPaths.clear(); + _magicDrawChangeStack.clear(); // <--- ADD THIS }); } } void _addGeneratedImage(String? newImageUrl) { - if (newImageUrl != null) { - debugPrint("✅ Adding generated image to canvas: $newImageUrl"); - setState(() { - elements.add({ - 'id': 'gen_${DateTime.now().millisecondsSinceEpoch}', - 'type': 'file_image', - 'content': newImageUrl, - 'position': const Offset(0, 0), - 'size': _canvasSize, - 'rotation': 0.0, - }); - // Clear magic paths now that operation is done - _magicPaths.clear(); + if (newImageUrl != null) { + debugPrint("✅ Adding generated image to canvas: $newImageUrl"); + setState(() { + elements.add({ + 'id': 'gen_${DateTime.now().millisecondsSinceEpoch}', + 'type': 'file_image', + 'content': newImageUrl, + 'position': const Offset(0, 0), + 'size': _canvasSize, + 'rotation': 0.0, }); - } + // Clear magic paths now that operation is done + _magicPaths.clear(); + }); + } } // Updated to generate mask as: Base Image + Drawing Strokes @@ -1365,9 +1516,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { final codec = await ui.instantiateImageCodec(data); final frameInfo = await codec.getNextFrame(); final baseImage = frameInfo.image; - + paintImage( - canvas: canvas, + canvas: canvas, rect: Rect.fromLTWH(0, 0, size.width, size.height), image: baseImage, fit: BoxFit.cover, // Or contain, depending on your logic @@ -1384,7 +1535,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { for (final path in paths) { final paint = Paint() - ..color = path.color // Use the drawing color (e.g. Blue) + ..color = + path + .color // Use the drawing color (e.g. Blue) ..strokeWidth = path.strokeWidth ..strokeCap = StrokeCap.round ..strokeJoin = StrokeJoin.round @@ -1398,11 +1551,9 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { } canvas.drawPath(p, paint); } else if (path.points.isNotEmpty) { - canvas.drawPoints( - ui.PointMode.points, - [path.points.first.offset], - paint, - ); + canvas.drawPoints(ui.PointMode.points, [ + path.points.first.offset, + ], paint); } } @@ -1426,16 +1577,24 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { } } - // Just toggles state; drawing data stays in _paths and is saved via _saveCanvas Future<void> _saveAndCloseMagicDraw() async { setState(() { _isMagicDrawActive = false; - _magicPaths.clear(); // Clear magic strokes if canceled + _magicPaths.clear(); + _magicDrawChangeStack.clear(); _tempBaseImage = null; }); } PreferredSizeWidget _buildAppBar() { + final bool canUndo = + _isMagicDrawActive + ? _magicDrawChangeStack.canUndo + : _changeStack.canUndo; + final bool canRedo = + _isMagicDrawActive + ? _magicDrawChangeStack.canRedo + : _changeStack.canRedo; return AppBar( leadingWidth: 160, leading: SafeArea( @@ -1452,7 +1611,7 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ), onPressed: () { if (_isMagicDrawActive) { - _saveAndCloseMagicDraw(); + _handleMagicDrawExit(); } else { _handleBackNavigation(); } @@ -1468,8 +1627,13 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ), ), onPressed: - _changeStack.canUndo - ? () => setState(() => _changeStack.undo()) + canUndo + ? () => setState( + () => + _isMagicDrawActive + ? _magicDrawChangeStack.undo() + : _changeStack.undo(), + ) : null, ), IconButton( @@ -1482,8 +1646,13 @@ class _CanvasBoardPageState extends State<CanvasBoardPage> { ), ), onPressed: - _changeStack.canRedo - ? () => setState(() => _changeStack.redo()) + canRedo + ? () => setState( + () => + _isMagicDrawActive + ? _magicDrawChangeStack.redo() + : _changeStack.redo(), + ) : null, ), ], @@ -1849,12 +2018,12 @@ class CanvasPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint()); - + // Draw normal paths for (final path in paths) { _drawPath(canvas, path); } - + // Draw magic paths (unless hidden by empty list passed in) for (final path in magicPaths) { _drawPath(canvas, path); @@ -1880,26 +2049,24 @@ class CanvasPainter extends CustomPainter { } void _drawPath(Canvas canvas, DrawingPath path) { - final paint = - Paint() - ..color = path.isEraser ? Colors.transparent : path.color - ..blendMode = path.isEraser ? BlendMode.clear : BlendMode.srcOver - ..strokeWidth = path.strokeWidth - ..strokeCap = StrokeCap.round - ..strokeJoin = StrokeJoin.round - ..style = PaintingStyle.stroke; - - if (path.points.length > 1) { - final Path p = Path(); - p.moveTo(path.points.first.offset.dx, path.points.first.offset.dy); - for (int i = 1; i < path.points.length; i++) - p.lineTo(path.points[i].offset.dx, path.points[i].offset.dy); - canvas.drawPath(p, paint); - } else if (path.points.isNotEmpty) { - canvas.drawPoints(ui.PointMode.points, [ - path.points.first.offset, - ], paint); - } + final paint = + Paint() + ..color = path.isEraser ? Colors.transparent : path.color + ..blendMode = path.isEraser ? BlendMode.clear : BlendMode.srcOver + ..strokeWidth = path.strokeWidth + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..style = PaintingStyle.stroke; + + if (path.points.length > 1) { + final Path p = Path(); + p.moveTo(path.points.first.offset.dx, path.points.first.offset.dy); + for (int i = 1; i < path.points.length; i++) + p.lineTo(path.points[i].offset.dx, path.points[i].offset.dy); + canvas.drawPath(p, paint); + } else if (path.points.isNotEmpty) { + canvas.drawPoints(ui.PointMode.points, [path.points.first.offset], paint); + } } @override diff --git a/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart b/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +// Note: You must ensure flutter_svg is imported in this file and installed in pubspec.yaml +import 'package:flutter_svg/flutter_svg.dart'; class MagicDrawTools extends StatefulWidget { final bool isActive; @@ -10,9 +12,12 @@ class MagicDrawTools extends StatefulWidget { final Function(double) onWidthChanged; final Function(bool) onEraserToggle; final VoidCallback onClose; - final Function(String) onPromptSubmit; // Callback for prompt submission - final bool isProcessing; // Loading state + final Function(String) onPromptSubmit; + final bool isProcessing; final List<Color> brandColors; + final Function(bool) onViewModeToggle; + final Function(bool) onMagicPanelActivityToggle; + final bool isMagicPanelDisabled; const MagicDrawTools({ super.key, @@ -27,6 +32,9 @@ class MagicDrawTools extends StatefulWidget { required this.onPromptSubmit, required this.isProcessing, required this.brandColors, + required this.onViewModeToggle, + required this.onMagicPanelActivityToggle, + required this.isMagicPanelDisabled, }); @override @@ -37,7 +45,6 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { bool _showStrokeSlider = false; final TextEditingController _promptController = TextEditingController(); - // Initialize recent colors here so they persist final List<Color> _recentColors = [ Colors.blue, Colors.purple, @@ -46,21 +53,33 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { Colors.amber, ]; + bool _isViewMode = false; + @override void dispose() { + if (_isViewMode) { + widget.onViewModeToggle(false); + } _promptController.dispose(); super.dispose(); } void _handleSubmit() { - if (_promptController.text.trim().isNotEmpty && !widget.isProcessing) { + if (_promptController.text.trim().isNotEmpty && + !widget.isProcessing && + !widget.isMagicPanelDisabled) { widget.onPromptSubmit(_promptController.text.trim()); - // Optional: Clear text after submit or keep it? - // Usually keeping it is better for iterations, clearing if successful. - // We'll let the parent decide or just keep it for now. } } + void _toggleViewMode() { + final newViewMode = !_isViewMode; + setState(() { + _isViewMode = newViewMode; + }); + widget.onViewModeToggle(newViewMode); + } + @override Widget build(BuildContext context) { if (!widget.isActive) return const SizedBox.shrink(); @@ -80,23 +99,50 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { ); } + Widget _buildToolIcon(dynamic icon, bool isActive, VoidCallback onTap) { + final Color activeColor = Colors.black; + final Color inactiveColor = Colors.grey; + final Color iconColor = isActive ? activeColor : inactiveColor; + + final Widget iconWidget = + (icon is IconData) + ? Icon(icon, size: 20, color: iconColor) + : SvgPicture.asset( + icon, + width: 20, + height: 20, + colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn), + ); + + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: isActive ? Colors.grey.shade200 : Colors.transparent, + shape: BoxShape.circle, + ), + child: iconWidget, + ), + ); + } + Widget _buildMagicDrawPanel() { return Container( decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(24), - boxShadow: [ + borderRadius: BorderRadius.circular(16), + boxShadow: const [ BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 15, - offset: const Offset(0, 5), + color: Colors.black12, + blurRadius: 12, + offset: Offset(0, 4), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ - // Prompt Bar Padding( padding: const EdgeInsets.all(8.0), child: Row( @@ -136,49 +182,99 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { color: Color(0xFF2B2B2B), shape: BoxShape.circle, ), - child: widget.isProcessing - ? const Padding( - padding: EdgeInsets.all(12.0), - child: CircularProgressIndicator( + child: + widget.isProcessing + ? const Padding( + padding: EdgeInsets.all(12.0), + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ) + : const Icon( + Icons.auto_awesome, color: Colors.white, - strokeWidth: 2, + size: 20, ), - ) - : const Icon( - Icons.auto_awesome, - color: Colors.white, - size: 20, - ), ), ), ], ), ), Divider(height: 1, color: Colors.grey.shade200), - // Tools Row Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildToolIcon(Icons.crop_free, false, () {}), - _buildToolIcon(Icons.brush, !widget.isEraser, () { - widget.onEraserToggle(false); + _buildToolIcon(Icons.pan_tool, widget.isMagicPanelDisabled, () { + setState(() => _showStrokeSlider = false); + + // Deactivate drawing tools when hand icon is activated + + if (!widget.isMagicPanelDisabled) { + widget.onEraserToggle(false); + } + + widget.onMagicPanelActivityToggle( + !widget.isMagicPanelDisabled, + ); }), + _buildToolIcon( + 'assets/icons/brush-line.svg', + + !widget.isEraser && + !_isViewMode && + !widget.isMagicPanelDisabled, + + () { + setState(() => _showStrokeSlider = false); + + if (_isViewMode) { + setState(() => _isViewMode = false); + + widget.onViewModeToggle(false); + } + + // Deactivate hand icon when brush is activated + + if (widget.isMagicPanelDisabled) { + widget.onMagicPanelActivityToggle(false); + } + + widget.onEraserToggle(false); + }, + ), - // Color Picker Trigger GestureDetector( - onTap: () => _showAdvancedColorPicker(context), + onTap: () { + setState(() => _showStrokeSlider = false); + + if (_isViewMode) { + setState(() => _isViewMode = false); + + widget.onViewModeToggle(false); + } + + _showAdvancedColorPicker(context); + }, + child: Container( width: 28, + height: 28, + decoration: BoxDecoration( color: widget.selectedColor, + shape: BoxShape.circle, + border: Border.all(color: Colors.white, width: 2), + boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), + blurRadius: 4, ), ], @@ -187,31 +283,64 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { ), GestureDetector( - onTap: () => setState( - () => _showStrokeSlider = !_showStrokeSlider, - ), + onTap: () { + if (_isViewMode) { + setState(() => _isViewMode = false); + + widget.onViewModeToggle(false); + } + + setState(() => _showStrokeSlider = !_showStrokeSlider); + }, + child: Container( padding: const EdgeInsets.all(8), + decoration: BoxDecoration( - color: _showStrokeSlider - ? Colors.grey.shade200 - : Colors.transparent, + color: + _showStrokeSlider + ? Colors.grey.shade200 + : Colors.transparent, + shape: BoxShape.circle, ), + child: Container( width: 10, + height: 10, + decoration: const BoxDecoration( color: Colors.black87, + shape: BoxShape.circle, ), ), ), ), + _buildToolIcon( - Icons.cleaning_services_outlined, - widget.isEraser, + 'assets/icons/eraser-line.svg', + + widget.isEraser && + !_isViewMode && + !widget.isMagicPanelDisabled, + () { + setState(() => _showStrokeSlider = false); + + if (_isViewMode) { + setState(() => _isViewMode = false); + + widget.onViewModeToggle(false); + } + + // Deactivate hand icon when eraser is activated + + if (widget.isMagicPanelDisabled) { + widget.onMagicPanelActivityToggle(false); + } + widget.onEraserToggle(true); }, ), @@ -223,20 +352,6 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { ); } - Widget _buildToolIcon(IconData icon, bool isActive, VoidCallback onTap) { - return GestureDetector( - onTap: onTap, - child: Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: isActive ? Colors.grey.shade200 : Colors.transparent, - shape: BoxShape.circle, - ), - child: Icon(icon, size: 20, color: Colors.black54), - ), - ); - } - Widget _buildTaperedStrokeSlider() { return Container( width: 250, @@ -269,8 +384,8 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { width: 210, child: Slider( value: widget.strokeWidth, - min: 2.0, - max: 30.0, + min: 5.0, + max: 60.0, onChanged: widget.onWidthChanged, ), ), @@ -300,9 +415,10 @@ class _MagicDrawToolsState extends State<MagicDrawTools> { class _TaperedSliderPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = const Color(0xFF2B2B2B) - ..style = PaintingStyle.fill; + final paint = + Paint() + ..color = const Color(0xFF2B2B2B) + ..style = PaintingStyle.fill; final path = Path(); path.moveTo(10, size.height / 2 - 2); path.lineTo(size.width - 10, size.height / 2 - 10); @@ -340,7 +456,6 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> late Color _currentColor; late List<Color> _brandPalette; - // Controllers for RGB Sliders (Integrated from other branch) final TextEditingController _rController = TextEditingController(); final TextEditingController _gController = TextEditingController(); final TextEditingController _bController = TextEditingController(); @@ -350,7 +465,7 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> super.initState(); _currentColor = widget.initialColor; _tabController = TabController(length: 3, vsync: this); - _brandPalette = widget.brandColors; // Integrate real brand colors + _brandPalette = widget.brandColors; _updateControllers(); } @@ -372,14 +487,11 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> void _updateColor(Color color) { setState(() { _currentColor = color; - - // Update recent colors list widget.recentColors.removeWhere((c) => c.value == color.value); widget.recentColors.insert(0, color); if (widget.recentColors.length > 5) { widget.recentColors.removeLast(); } - _updateControllers(); }); widget.onColorChanged(color); @@ -396,7 +508,6 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - // --- HEADER HANDLE --- const SizedBox(height: 12), Center( child: Container( @@ -409,8 +520,6 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> ), ), const SizedBox(height: 16), - - // --- TABS --- TabBar( controller: _tabController, labelColor: Colors.black, @@ -427,10 +536,7 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> Tab(text: 'Sliders'), ], ), - const SizedBox(height: 16), - - // --- HEX / HEADER ROW --- Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( @@ -499,11 +605,8 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> ], ), ), - const SizedBox(height: 16), const Divider(height: 1), - - // --- MAIN CONTENT AREA --- Expanded( child: TabBarView( controller: _tabController, @@ -515,34 +618,25 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> ], ), ), - - // --- FOOTER --- _buildSharedFooter(), ], ), ); } - // --- HELPER: Generate colors (Integrated from other branch) --- List<Color> _generateColorGrid() { List<Color> colors = []; - - // 1. Top Row: Grayscale (White -> Black) for (int i = 0; i < 9; i++) { - double lightness = 1.0 - (i / 8); // 1.0 to 0.0 + double lightness = 1.0 - (i / 8); colors.add(HSLColor.fromAHSL(1.0, 0.0, 0.0, lightness).toColor()); } - - // 2. Main Grid: Hues (Columns) x Shades (Rows) - final int hueSteps = 9; // Columns - final int shadeSteps = 7; // Rows excluding grayscale - + final int hueSteps = 9; + final int shadeSteps = 7; for (int shade = 0; shade < shadeSteps; shade++) { for (int hueStep = 0; hueStep < hueSteps; hueStep++) { double hue = (hueStep / hueSteps) * 360; double saturation = 0.5 + (shade / shadeSteps) * 0.5; double lightness = 0.8 - (shade / shadeSteps) * 0.5; - colors.add( HSLColor.fromAHSL(1.0, hue, saturation, lightness).toColor(), ); @@ -657,14 +751,11 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> _updateColor(_currentColor.withRed(v.toInt())); }), const SizedBox(height: 24), - _buildSingleRGBSlider( - "Green", - Colors.green, - _currentColor.green, - (v) { - _updateColor(_currentColor.withGreen(v.toInt())); - }, - ), + _buildSingleRGBSlider("Green", Colors.green, _currentColor.green, ( + v, + ) { + _updateColor(_currentColor.withGreen(v.toInt())); + }), const SizedBox(height: 24), _buildSingleRGBSlider("Blue", Colors.blue, _currentColor.blue, (v) { _updateColor(_currentColor.withBlue(v.toInt())); @@ -834,4 +925,4 @@ class _AdvancedColorPickerSheetState extends State<_AdvancedColorPickerSheet> child: const Icon(Icons.add, size: 20, color: Colors.black54), ); } -} -\ No newline at end of file +}