From 8a3f8751b678038b77e2e6130fef4c0672565961 Mon Sep 17 00:00:00 2001 From: PabloRufianJimenez Date: Wed, 8 Oct 2025 18:20:56 +0200 Subject: [PATCH 1/2] `LassoSelectTool` and `SelectTool` show the outlines of the previous and applied selection when drawing. --- Pinta.Core/Classes/BaseTool.cs | 11 +++++++ Pinta.Core/Classes/DocumentSelection.cs | 3 +- .../Widgets/Canvas/PintaCanvas.cs | 29 +++++++++++++++---- Pinta.Tools/Tools/LassoSelectTool.cs | 6 ++++ Pinta.Tools/Tools/SelectTool.cs | 12 ++++++++ 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/Pinta.Core/Classes/BaseTool.cs b/Pinta.Core/Classes/BaseTool.cs index 965d310683..cb0a6fcc58 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 7b3eccba82..57efae8878 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 9b5e1adb4b..73c016a2d7 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 daf7f260c1..d57b877c28 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 9b46109913..ec022cecb1 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,9 @@ 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 +226,10 @@ private void AfterSelectionChange (object? sender, EventArgs event_args) LoadFromDocument (workspace.ActiveDocument); } + private List>? applied_selection_polygons = null; + public override List>? AppliedSelectionPolygons => + combine_mode != CombineMode.Replace ? applied_selection_polygons : null; + /// /// Initialize from the document's selection. /// @@ -229,5 +238,8 @@ private void LoadFromDocument (Document document) DocumentSelection selection = document.Selection; handle.Rectangle = selection.HandleBounds; ShowHandles (document.Selection.Visible && tools.CurrentTool == this); + //This is a placeholder solution. Ideally, this should be assigned the new shape of the applied selection according to the new bounds. + //However, SelectTool doesn't provide any method that returns the applied selection without modifying the selection. + applied_selection_polygons = new List> (selection.SelectionPolygons); } } From 0411fd3e97abd8e1331eec7bfba52a7b3ab39728 Mon Sep 17 00:00:00 2001 From: PabloRufianJimenez Date: Thu, 9 Oct 2025 18:01:15 +0200 Subject: [PATCH 2/2] `SelectTool` only shows outlines when dragging --- Pinta.Tools/Tools/SelectTool.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Pinta.Tools/Tools/SelectTool.cs b/Pinta.Tools/Tools/SelectTool.cs index ec022cecb1..d3dd18343e 100644 --- a/Pinta.Tools/Tools/SelectTool.cs +++ b/Pinta.Tools/Tools/SelectTool.cs @@ -182,7 +182,6 @@ 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. @@ -228,7 +227,7 @@ private void AfterSelectionChange (object? sender, EventArgs event_args) private List>? applied_selection_polygons = null; public override List>? AppliedSelectionPolygons => - combine_mode != CombineMode.Replace ? applied_selection_polygons : null; + handle.IsDragging && combine_mode != CombineMode.Replace ? applied_selection_polygons : null; /// /// Initialize from the document's selection. @@ -238,8 +237,5 @@ private void LoadFromDocument (Document document) DocumentSelection selection = document.Selection; handle.Rectangle = selection.HandleBounds; ShowHandles (document.Selection.Visible && tools.CurrentTool == this); - //This is a placeholder solution. Ideally, this should be assigned the new shape of the applied selection according to the new bounds. - //However, SelectTool doesn't provide any method that returns the applied selection without modifying the selection. - applied_selection_polygons = new List> (selection.SelectionPolygons); } }