Skip to content

Commit c6b8d1c

Browse files
authored
Add handle to show clone stamp origin (#2190)
Fixes #1923
1 parent ead3e7a commit c6b8d1c

2 files changed

Lines changed: 124 additions & 2 deletions

File tree

Pinta.Tools/Handles/BrushHandle.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using System.Drawing;
3+
using Gsk;
4+
using Gtk;
5+
using Pinta.Core;
6+
7+
namespace Pinta.Tools;
8+
9+
// That handle is used as second brush to show position of stamp tool origin
10+
public class BrushHandle : IToolHandle
11+
{
12+
private int brush_width;
13+
public int BrushWidth {
14+
get { return brush_width; }
15+
set {
16+
if (value > 0)
17+
brush_width = value;
18+
}
19+
}
20+
public bool Active { get; set; }
21+
public PointD CanvasPosition { get; set; }
22+
private readonly IWorkspaceService workspace;
23+
24+
public BrushHandle (IWorkspaceService workspace)
25+
{
26+
this.workspace = workspace;
27+
}
28+
29+
public bool ContainsPoint (PointD windowPoint)
30+
=> ComputeWindowRect ().ContainsPoint (windowPoint);
31+
32+
/// <summary>
33+
/// Drawing shape like in GdkExtensions.CreateIconWithShape
34+
/// </summary>
35+
public void Draw (Snapshot snapshot)
36+
{
37+
double zoom =
38+
(PintaCore.Workspace.HasOpenDocuments)
39+
? Math.Min (30d, workspace.GetScale ())
40+
: 1d;
41+
int clampedWidth = (int) Math.Min (800d, brush_width * zoom);
42+
43+
if (clampedWidth < 3)
44+
return;
45+
46+
int halfOfShapeWidth = clampedWidth / 2;
47+
48+
RectangleF shapeRect = new () {
49+
X = (float) (CanvasPosition.X * zoom),
50+
Y = (float) (CanvasPosition.Y * zoom),
51+
Width = clampedWidth,
52+
Height = clampedWidth
53+
};
54+
55+
Gdk.RGBA outerColor = new () {
56+
Red = 1.0f,
57+
Green = 1.0f,
58+
Blue = 1.0f,
59+
Alpha = .75f
60+
};
61+
Gdk.RGBA innerColor = new () {
62+
Red = 0,
63+
Green = 0,
64+
Blue = 0,
65+
Alpha = 1.0f
66+
};
67+
68+
PathBuilder pathBuilder = PathBuilder.New ();
69+
var originPosition = new Graphene.Point ();
70+
originPosition.Init (shapeRect.X, shapeRect.Y);
71+
pathBuilder.AddCircle (originPosition, halfOfShapeWidth);
72+
Stroke stroke = Stroke.New (2);
73+
snapshot.AppendStroke (pathBuilder.ToPath (), stroke, outerColor);
74+
75+
shapeRect.Inflate (-1, -1);
76+
pathBuilder.AddCircle (originPosition, halfOfShapeWidth);
77+
stroke.SetLineWidth (1);
78+
snapshot.AppendStroke (pathBuilder.ToPath (), stroke, innerColor);
79+
}
80+
81+
public RectangleI InvalidateRect => ComputeWindowRect ().Inflated (2, 2).ToInt ();
82+
83+
/// <summary>
84+
/// Bounding rectangle of the handle (in window space). Similar to MoveHandle.
85+
/// </summary>
86+
87+
private RectangleD ComputeWindowRect ()
88+
{
89+
double diameter = brush_width;
90+
double radius = diameter / 2.0;
91+
92+
PointD windowPt = workspace.CanvasPointToView (CanvasPosition);
93+
return new RectangleD (windowPt.X - radius, windowPt.Y - radius, diameter, diameter);
94+
}
95+
}

Pinta.Tools/Tools/CloneStampTool.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// THE SOFTWARE.
2626

2727
using System;
28+
using System.Collections.Generic;
2829
using Gdk;
2930
using Pinta.Core;
3031

@@ -39,11 +40,15 @@ public sealed class CloneStampTool : BaseBrushTool
3940

4041
private readonly SystemManager system_manager;
4142
private readonly IWorkspaceService workspace;
43+
private readonly BrushHandle handle;
44+
private bool move_origin_handle = true;
4245
public CloneStampTool (IServiceProvider services) : base (services)
4346
{
4447
system_manager = services.GetService<SystemManager> ();
4548
workspace = services.GetService<IWorkspaceService> ();
4649

50+
handle = new BrushHandle (workspace);
51+
4752
// Update cursor on zoom
4853
workspace.ViewSizeChanged += (_, _) => {
4954
if (IsActiveTool ()) {
@@ -59,6 +64,7 @@ public CloneStampTool (IServiceProvider services) : base (services)
5964
public override Gdk.Key ShortcutKey => new (Gdk.Constants.KEY_L);
6065
public override int Priority => 47;
6166
protected override bool ShowAntialiasingButton => true;
67+
public override IEnumerable<IToolHandle> Handles => [handle];
6268

6369
public override Cursor DefaultCursor {
6470
get {
@@ -94,17 +100,23 @@ protected override void OnMouseDown (Document document, ToolMouseEventArgs e)
94100
} else {
95101
origin = e.Point;
96102
offset = null;
103+
UpdateOriginHandle (document, origin.Value.X, origin.Value.Y, false);
97104
}
98105
}
99106

100107
protected override void OnMouseMove (Document document, ToolMouseEventArgs e)
101108
{
102-
if (!painting || !offset.HasValue)
109+
if (!offset.HasValue)
103110
return;
104111

105112
var x = e.Point.X;
106113
var y = e.Point.Y;
107114

115+
UpdateOriginHandle (document, x - offset.Value.X, y - offset.Value.Y, true);
116+
117+
if (!painting)
118+
return;
119+
108120
if (!last_point.HasValue) {
109121
last_point = e.Point;
110122
return;
@@ -134,6 +146,9 @@ protected override void OnMouseUp (Document document, ToolMouseEventArgs e)
134146
{
135147
painting = false;
136148

149+
if (e.IsControlPressed)
150+
handle.Active = true;
151+
137152
using Cairo.Context g = new (document.Layers.CurrentUserLayer.Surface);
138153
g.SetSourceSurface (document.Layers.ToolLayer.Surface, 0, 0);
139154
g.Paint ();
@@ -152,6 +167,7 @@ protected override bool OnKeyDown (Document document, ToolKeyEventArgs e)
152167
{
153168
// Note that this WON'T work if user presses control key and THEN selects the tool!
154169
if (e.Key.IsControlKey ()) {
170+
move_origin_handle = false;
155171
SetCursor (Gdk.Cursor.NewFromTexture (Resources.GetIcon ("Cursor.CloneStampSetSource.png"), 16, 26, null));
156172
}
157173

@@ -160,14 +176,25 @@ protected override bool OnKeyDown (Document document, ToolKeyEventArgs e)
160176

161177
protected override bool OnKeyUp (Document document, ToolKeyEventArgs e)
162178
{
163-
if (e.Key.IsControlKey ())
179+
if (e.Key.IsControlKey ()) {
180+
move_origin_handle = true;
164181
SetCursor (DefaultCursor);
182+
}
165183

166184
return false;
167185
}
168186

169187
protected override void OnDeactivated (Document? document, BaseTool? newTool)
170188
{
171189
origin = null;
190+
handle.Active = false;
191+
}
192+
193+
private void UpdateOriginHandle (Document document, int x, int y, bool move_event)
194+
{
195+
if (move_origin_handle || (!move_event))
196+
handle.CanvasPosition = new (x, y);
197+
handle.BrushWidth = BrushWidth;
198+
document.Workspace.Invalidate (handle.InvalidateRect);
172199
}
173200
}

0 commit comments

Comments
 (0)