diff --git a/Pinta.Core/Classes/BaseTool.cs b/Pinta.Core/Classes/BaseTool.cs index 965d31068..cb0a6fcc5 100644 --- a/Pinta.Core/Classes/BaseTool.cs +++ b/Pinta.Core/Classes/BaseTool.cs @@ -28,6 +28,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using ClipperLib; using Gdk; using Gtk; @@ -119,6 +120,16 @@ public virtual bool IsEditableShapeTool public virtual IEnumerable Handles => []; + /// + /// For selection tools, returns the shape of the applied selection, i.e. the selection being combined with the previous selection. + /// When this method doesn't return null, a preview of the new selection is shown with the outlines of the previous and applied selection. + /// If this is not a selection tool, this should always return null + /// If this selection tool is not currently drawing a new selection or is in replace mode, this should return null. + /// + public virtual IReadOnlyList>? AppliedSelectionPolygons { + get => null; + } + /// /// The shortcut key used to activate this tool in the toolbox. /// Return Gdk.Key.Invalid for no shortcut key. diff --git a/Pinta.Core/Classes/DocumentSelection.cs b/Pinta.Core/Classes/DocumentSelection.cs index 7b3eccba8..57efae887 100644 --- a/Pinta.Core/Classes/DocumentSelection.cs +++ b/Pinta.Core/Classes/DocumentSelection.cs @@ -129,7 +129,7 @@ public static List> ConvertToPolygons (IReadOnlyList /// A Clipper Polygon collection. /// A Pinta Polygon set. - private static IReadOnlyList> ConvertToPolygonSet (IReadOnlyList> clipperPolygons) + public static IReadOnlyList> ConvertToPolygonSet (IReadOnlyList> clipperPolygons) { var resultingPolygonSet = new PointI[clipperPolygons.Count][]; @@ -152,6 +152,7 @@ private static IReadOnlyList> ConvertToPolygonSet (IReadOn return resultingPolygonSet; } + /// /// Return a transformed copy of the selection. /// diff --git a/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs b/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs index 9b5e1adb4..73c016a2d 100644 --- a/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs +++ b/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs @@ -27,6 +27,8 @@ using System; using System.Collections.Generic; using System.Linq; +using Cairo; +using ClipperLib; using Pinta.Core; namespace Pinta.Gui.Widgets; @@ -224,9 +226,24 @@ private void DrawSelection (Gtk.Snapshot snapshot, Graphene.Rect canvasViewBound bool fillSelection = tools.CurrentTool?.IsSelectionTool ?? false; // Convert the selection path. - Gsk.PathBuilder pathBuilder = Gsk.PathBuilder.New (); - pathBuilder.AddCairoPath (document.Selection.SelectionPath); - Gsk.Path selectionPath = pathBuilder.ToPath (); + Gsk.PathBuilder fillPathBuilder = Gsk.PathBuilder.New (); + fillPathBuilder.AddCairoPath (document.Selection.SelectionPath); + Gsk.Path fillPath = fillPathBuilder.ToPath (); + + Gsk.Path strokePath; + IReadOnlyList>? appliedPolygons = tools.CurrentTool?.AppliedSelectionPolygons; + //appliedPolygons not null means the tool wants to show a preview. + if (appliedPolygons is not null) { + //The preview is composed of the outlines of the previous selection and the applied selection. + var previewPolygons = appliedPolygons.Concat (document.PreviousSelection.SelectionPolygons).ToList (); + using Context g = CairoExtensions.CreatePathContext (); + Path outlinePath = g.CreatePolygonPath (DocumentSelection.ConvertToPolygonSet (previewPolygons)); + + Gsk.PathBuilder strokePathBuilder = Gsk.PathBuilder.New (); + strokePathBuilder.AddCairoPath (outlinePath); + strokePath = strokePathBuilder.ToPath (); + } else + strokePath = fillPath; snapshot.Save (); snapshot.PushClip (canvasViewBounds); @@ -238,18 +255,18 @@ private void DrawSelection (Gtk.Snapshot snapshot, Graphene.Rect canvasViewBound if (fillSelection) { Gdk.RGBA fillColor = new () { Red = 0.7f, Green = 0.8f, Blue = 0.9f, Alpha = 0.2f }; - snapshot.AppendFill (selectionPath, Gsk.FillRule.EvenOdd, fillColor); + snapshot.AppendFill (fillPath, Gsk.FillRule.EvenOdd, fillColor); } // Draw a white line first so it shows up on dark backgrounds Gsk.Stroke stroke = Gsk.Stroke.New (lineWidth: 1.0f / scale); Gdk.RGBA white = new () { Red = 1, Green = 1, Blue = 1, Alpha = 1 }; - snapshot.AppendStroke (selectionPath, stroke, white); + snapshot.AppendStroke (strokePath, stroke, white); // Draw a black dashed line over the white line stroke.SetDash ([2.0f / scale, 4.0f / scale]); Gdk.RGBA black = new () { Red = 0, Green = 0, Blue = 0, Alpha = 1 }; - snapshot.AppendStroke (selectionPath, stroke, black); + snapshot.AppendStroke (strokePath, stroke, black); snapshot.Pop (); snapshot.Restore (); diff --git a/Pinta.Tools/Tools/LassoSelectTool.cs b/Pinta.Tools/Tools/LassoSelectTool.cs index daf7f260c..d57b877c2 100644 --- a/Pinta.Tools/Tools/LassoSelectTool.cs +++ b/Pinta.Tools/Tools/LassoSelectTool.cs @@ -155,6 +155,9 @@ private void FinalizeShape (Document document) hist = null; } lasso_polygon.Clear (); + + //To make sure the preview doesn't show anymore + document.Workspace.Invalidate (); } protected override void OnDeactivated (Document? document, BaseTool? newTool) @@ -203,6 +206,9 @@ protected override void OnCommit (Document? document) FinalizeShape (document); } + public override List>? AppliedSelectionPolygons => + lasso_polygon.Count > 0 && combine_mode != CombineMode.Replace ? [lasso_polygon] : null; + protected override void OnSaveSettings (ISettingsService settings) { base.OnSaveSettings (settings); diff --git a/Pinta.Tools/Tools/SelectTool.cs b/Pinta.Tools/Tools/SelectTool.cs index 9b4610991..d3dd18343 100644 --- a/Pinta.Tools/Tools/SelectTool.cs +++ b/Pinta.Tools/Tools/SelectTool.cs @@ -28,6 +28,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using ClipperLib; using Pinta.Core; namespace Pinta.Tools; @@ -108,6 +109,7 @@ protected override void OnMouseMove (Document document, ToolMouseEventArgs e) RectangleI dirty = ReDraw (document); + SelectionModeHandler.PerformSelectionMode (document, combine_mode, document.Selection.SelectionPolygons); document.Workspace.Invalidate (dirty.Union (last_dirty)); @@ -180,6 +182,8 @@ private RectangleI ReDraw (Document document) DrawShape (document, rect, document.Layers.SelectionLayer); + applied_selection_polygons = new List> (document.Selection.SelectionPolygons); + // Figure out a bounding box for everything that was drawn, and add a bit of padding. RectangleI dirty = rect.ToInt (); dirty = dirty.Inflated (2, 2); @@ -221,6 +225,10 @@ private void AfterSelectionChange (object? sender, EventArgs event_args) LoadFromDocument (workspace.ActiveDocument); } + private List>? applied_selection_polygons = null; + public override List>? AppliedSelectionPolygons => + handle.IsDragging && combine_mode != CombineMode.Replace ? applied_selection_polygons : null; + /// /// Initialize from the document's selection. ///