1414/*
1515 * TODO — ImageZoom & SelectionAdorner Architecture Roadmap
1616 * --------------------------------------------------------
17+ * CURRENT STATUS: Functional "Monolithic" Pattern.
18+ * NEXT STEP: Refactor to "State/Strategy" Pattern (IToolHandler).
1719 *
1820 * 1. INPUT / EVENT SYSTEM
1921 * -----------------------
20- * [ ] Move input handling (mouse down/move/up) from SelectionAdorner into ImageZoom
22+ * [ ] Move input handling (mouse down/move/up) completely into ImageZoom
23+ * (currently shared/delegated to Adorner)
2124 * [ ] Add a unified input dispatcher that forwards events to the current tool
2225 * [ ] Implement ToolContext to carry image transforms, image size, modifiers (Ctrl/Shift)
2326 *
2427 *
25- * 2. TOOL SYSTEM REFINEMENT
26- * -------------------------
28+ * 2. TOOL SYSTEM REFINEMENT (The "Switch Statement" Refactor)
29+ * -----------------------------------------------------------
2730 * [ ] Introduce IToolHandler interface:
28- * - OnMouseDown / OnMouseMove / OnMouseUp
29- * - RenderOverlay(DrawingContext dc)
30- * - Cancel () / Reset()
31+ * - OnMouseDown / OnMouseMove / OnMouseUp
32+ * - RenderOverlay(DrawingContext dc)
33+ * - GetFrame () / Reset()
3134 *
32- * [ ] Convert Rectangle, Ellipse, Dot, Trace, FreeForm into dedicated tool handler classes
35+ * [ ] Convert Rectangle, Ellipse, Dot, Trace, FreeForm into dedicated tool classes
36+ * (e.g., RectangleTool.cs, FreeFormTool.cs) to remove the massive switch statements.
3337 * [ ] Add ToolState / ToolSession object to store points, frame, geometry
34- * [ ] Decouple selection logic from the adorner (adorner draws only)
38+ * [ ] Decouple selection logic from the adorner (Adorner should only DRAW, not hold data )
3539 *
3640 *
3741 * 3. TRANSFORM PIPELINE
3842 * ---------------------
39- * [ ] Replace single Transform with a TransformGroup:
40- * - ZoomTransform
41- * - ScrollOffsetTransform
42- * - RotationTransform (future use)
43- * - CropTransform (future use)
44- *
43+ * [ ] Replace manual Matrix math with a TransformGroup:
44+ * - ZoomTransform
45+ * - ScrollOffsetTransform
4546 * [ ] Store both:
46- * - DrawingTransform (image → screen)
47- * - InputTransform (screen → image/pixel space)
48- *
49- * [ ] Update SelectionFrame to always use pixel-space coordinates
47+ * - DrawingTransform (image → screen)
48+ * - InputTransform (screen → image/pixel space)
49+ * [-] Update SelectionFrame to always use pixel-space coordinates
50+ * (Currently handled via conversion in ImageProcessor.FillArea, but Frame itself stores WPF Points)
5051 *
5152 *
5253 * 4. ADORNER IMPROVEMENTS
5354 * ------------------------
54- * [ ] Restrict adorner to visualization-only duties
55- * [ ] Let adorner read data from current IToolHandler instead of owning points
56- * [ ] Add double-buffering for smoother overlay drawing (optional)
57- * [ ] Support drawing tool-specific overlays (lasso, handles, marquee)
55+ * [ ] Restrict adorner to visualization-only duties (View)
56+ * [ ] Let adorner read data from current IToolHandler instead of owning the Point Lists
57+ * [ ] Add double-buffering or DrawingVisual for smoother overlay drawing (optional)
5858 *
5959 *
60- * 5. IMAGE OPERATIONS (FUTURE)
61- * ----------------------------
62- * [ ] Integrate DirectBitmapImage operations into tool handlers (fill, stroke, erase)
63- * [ ] Add support for brush size, brush hardness, opacity
64- * [ ] Add selection commit logic (crop, cut, fill, invert, delete, outline)
60+ * 5. IMAGE OPERATIONS (COMPLETED / INTEGRATED)
61+ * --------------------------------------------
62+ * [x] Integrate DirectBitmapImage operations (Done via ImageProcessor & ImageView)
63+ * [x] Add selection commit logic (Done via SelectionAdorner.CaptureAndClear & Canvas_MouseUp)
64+ * [x] Support FreeForm Polygon filling (Done via auto-close logic in CaptureAndClear)
65+ * [ ] Add support for brush size/hardness visualization in the Adorner
6566 * [ ] Add pixel-snapping modes (whole pixel alignment when zoomed)
6667 *
6768 *
68- * 6. EXTENDED TOOLSET (OPTIONAL NICE-TO-HAVE )
69- * -------------------------------------------
70- * [ ] Polygonal lasso tool
71- * [ ] Magic-wand / flood-fill selection using your existing flood-fill helper
69+ * 6. EXTENDED TOOLSET (FUTURE )
70+ * ----------------------------
71+ * [ ] Polygonal lasso tool (Click-to-add-point)
72+ * [ ] Magic-wand / flood-fill selection ( using existing flood-fill helper)
7273 * [ ] Text tool (typed overlay rendered to bitmap)
7374 * [ ] Stamp/cloning tool
7475 * [ ] Multi-layer support (background, overlay layers)
7778 * 7. PERFORMANCE & ARCHITECTURE
7879 * ------------------------------
7980 * [ ] Add invalidate throttling (Redraw only when needed)
80- * [ ] Use DrawingVisual for overlays to reduce Adorner overhead
81+ * [x] Clear "Ghost Frames" immediately after drawing (Done via CaptureAndClear)
8182 * [ ] Add high-DPI support for Zoom + PixelGrid alignment
82- * [ ] Allow async pixel operations (large flood fills, transforms)
83- *
83+ * [ ] Allow async pixel operations for large fills
8484 *
8585 * END TODO LIST
8686 */
8787
8888
8989using System ;
90+ using System . Collections . Generic ;
9091using System . Diagnostics ;
9192using System . IO ;
9293using System . Windows ;
@@ -115,6 +116,10 @@ public sealed partial class ImageZoom : IDisposable
115116 /// <param name="point">The point.</param>
116117 public delegate void DelegatePoint ( Point point ) ;
117118
119+ public delegate void DelegateMultiFrame ( List < SelectionFrame > frames ) ;
120+
121+ public event DelegateMultiFrame SelectedMultiFrames ;
122+
118123 /// <summary>
119124 /// The image source property
120125 /// </summary>
@@ -156,6 +161,22 @@ public sealed partial class ImageZoom : IDisposable
156161 typeof ( ImageZoom ) ,
157162 new PropertyMetadata ( 1.0 , OnZoomScaleChanged ) ) ;
158163
164+ /// <summary>
165+ /// The selected multi-frames command property
166+ /// </summary>
167+ public static readonly DependencyProperty SelectedMultiFramesCommandProperty =
168+ DependencyProperty . Register ( nameof ( SelectedMultiFramesCommand ) , typeof ( ICommand ) , typeof ( ImageZoom ) ,
169+ new PropertyMetadata ( null ) ) ;
170+
171+ /// <summary>
172+ /// Gets or sets the selected multi-frames command.
173+ /// </summary>
174+ public ICommand SelectedMultiFramesCommand
175+ {
176+ get => ( ICommand ) GetValue ( SelectedMultiFramesCommandProperty ) ;
177+ set => SetValue ( SelectedMultiFramesCommandProperty , value ) ;
178+ }
179+
159180 /// <summary>
160181 /// The image clicked command property
161182 /// </summary>
@@ -578,93 +599,46 @@ private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
578599 }
579600
580601 /// <summary>
581- /// Handles the MouseUp event of the Grid control.
602+ /// Handles the MouseUp event of the Grid control.
582603 /// </summary>
583604 /// <param name="sender">The source of the event.</param>
584- /// <param name="e">The <see cref="MouseButtonEventArgs" /> instance containing the event data.</param>
605+ /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
585606 private void Canvas_MouseUp ( object sender , MouseButtonEventArgs e )
586607 {
587- // Release the mouse capture and stop tracking it.
588608 _mouseDown = false ;
589609 MainCanvas . ReleaseMouseCapture ( ) ;
590610
591- if ( SelectionAdorner == null )
592- {
593- Trace . Write ( ComCtlResources . InformationArdonerNull ) ;
594- return ;
595- }
611+ if ( SelectionAdorner == null ) return ;
596612
597- switch ( SelectionTool )
613+ // 1. Identify "Immediate Action" tools (Drawing tools)
614+ bool isDrawingTool = SelectionTool == ImageZoomTools . Rectangle ||
615+ SelectionTool == ImageZoomTools . Ellipse ||
616+ SelectionTool == ImageZoomTools . FreeForm ||
617+ SelectionTool == ImageZoomTools . Trace ||
618+ SelectionTool == ImageZoomTools . Dot ;
619+
620+ if ( isDrawingTool )
598621 {
599- case ImageZoomTools . Move :
600- // No specific action required for Move
601- break ;
622+ // 2. Capture the data AND Clear the visuals immediately
623+ // This prevents the "Ghost Frame" from sticking around
624+ SelectionFrame frame = SelectionAdorner . CaptureAndClear ( ) ;
602625
603- case ImageZoomTools . Rectangle :
604- case ImageZoomTools . Ellipse :
626+ // 3. Validation: Ensure we actually drew something substantial
627+ bool isValid = ( frame . Width > 0 && frame . Height > 0 ) ||
628+ ( frame . Points != null && frame . Points . Count > 0 ) ;
605629
606- var frame = SelectionAdorner . CurrentSelectionFrame ;
607- SelectedFrame ? . Invoke ( frame ) ;
630+ if ( isValid )
631+ {
632+ // 4. Fire the Command to the ViewModel (Update the Bitmap)
608633 SafeExecuteCommand ( SelectedFrameCommand , frame ) ;
609- break ;
610- case ImageZoomTools . FreeForm :
611- // Get the mouse position relative to the image
612- var rawPoint = e . GetPosition ( BtmImage ) ;
613-
614- //TODO problem with our DPI and multiple Monitor Setup
615-
616- SelectionAdorner . FreeFormPoints . Add ( rawPoint ) ;
617- break ;
618-
619- case ImageZoomTools . Trace :
620- SelectionAdorner . IsTracing = false ;
621-
622- // Implement logic for FreeFormPoints
623- var points = SelectionAdorner . FreeFormPoints ;
624634
625- if ( points is { Count : > 0 } )
626- {
627- // Process the collected freeform points
628- SafeExecuteCommand ( SelectedFreeFormPointsCommand , points ) ;
629-
630- // Optionally, log or display the points
631- Trace . WriteLine ( $ "Trace tool completed with { points . Count } points.") ;
632- }
633-
634- break ;
635-
636- case ImageZoomTools . Dot :
637- SetClickedPoint ( e ) ;
638-
639- var endpoint = e . GetPosition ( BtmImage ) ;
640- SelectedPoint ? . Invoke ( endpoint ) ;
641- break ;
642- default :
643- // Do nothing for unsupported tools
644- return ;
645- }
646-
647- if ( SelectionAdorner == null )
648- {
649- return ;
635+ // 5. Fire the Event (if anything else is listening)
636+ SelectedFrame ? . Invoke ( frame ) ;
637+ }
650638 }
651-
652- // Get the AdornerLayer for the image
653- var adornerLayer = AdornerLayer . GetAdornerLayer ( BtmImage ) ;
654-
655- if ( adornerLayer != null && SelectionAdorner != null )
639+ else if ( SelectionTool == ImageZoomTools . Move )
656640 {
657- // Only remove the adorner for tools that represent a finished, one-shot selection
658- if ( SelectionTool is ImageZoomTools . Rectangle or ImageZoomTools . Ellipse or ImageZoomTools . Dot )
659- {
660- adornerLayer . Remove ( SelectionAdorner ) ;
661- SelectionAdorner = null ;
662- }
663- else
664- {
665- // Keep the adorner alive for Move, FreeForm, Trace so user can continue interacting
666- SelectionAdorner ? . UpdateImageTransform ( BtmImage . RenderTransform ) ;
667- }
641+ // Just release capture, do nothing else
668642 }
669643 }
670644
@@ -752,9 +726,37 @@ private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
752726 /// <param name="e">The <see cref="MouseButtonEventArgs" /> instance containing the event data.</param>
753727 private void Canvas_MouseRightButtonUp ( object sender , MouseButtonEventArgs e )
754728 {
755- if ( SelectionTool == ImageZoomTools . FreeForm )
729+ // 1. If we are currently dragging (Mouse Left is Down), cancel the current shape
730+ if ( _mouseDown )
731+ {
732+ _mouseDown = false ;
733+ MainCanvas . ReleaseMouseCapture ( ) ;
734+ // Optional: reset current points in Adorner without committing
735+ // SelectionAdorner.ResetCurrent();
736+ return ;
737+ }
738+
739+ // 2. If we are idle, Right Click means "I am finished selecting"
740+ if ( SelectionAdorner != null )
756741 {
757- CompleteFreeFormSelection ( ) ;
742+ // Get all collected frames
743+ var frames = SelectionAdorner . GetCommittedFrames ( ) ;
744+
745+ if ( frames . Count > 0 )
746+ {
747+ // Fire event with list of frames
748+ SelectedMultiFrames ? . Invoke ( frames ) ;
749+ // Execute command if you have a List version of the command
750+ // SafeExecuteCommand(SelectedMultiFramesCommand, frames);
751+ }
752+
753+ // Cleanup
754+ var adornerLayer = AdornerLayer . GetAdornerLayer ( BtmImage ) ;
755+ adornerLayer ? . Remove ( SelectionAdorner ) ;
756+ SelectionAdorner = null ;
757+
758+ // Optional: Reset tool to Move automatically?
759+ // SelectionTool = ImageZoomTools.Move;
758760 }
759761 }
760762
@@ -943,4 +945,4 @@ private static void SafeExecuteCommand(ICommand? cmd, object? parameter)
943945
944946 #endregion
945947 }
946- }
948+ }
0 commit comments