Skip to content

Commit 6b1ae6a

Browse files
committed
Initial work on using a rectangle handle for more intuitive resizing of selections
- Use the rectangle handle to define how the selected area is scaled - Update the rectangle handle when dragging to translate the selected area Bug: #585
1 parent 0c34148 commit 6b1ae6a

File tree

1 file changed

+104
-37
lines changed

1 file changed

+104
-37
lines changed

Pinta.Tools/Tools/BaseTransformTool.cs

Lines changed: 104 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
1-
//
1+
//
22
// BaseTransformTool.cs
3-
//
3+
//
44
// Author:
55
// Volodymyr <${AuthorEmail}>
6-
//
6+
//
77
// Copyright (c) 2012 Volodymyr
8-
//
8+
//
99
// Permission is hereby granted, free of charge, to any person obtaining a copy
1010
// of this software and associated documentation files (the "Software"), to deal
1111
// in the Software without restriction, including without limitation the rights
1212
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1313
// copies of the Software, and to permit persons to whom the Software is
1414
// furnished to do so, subject to the following conditions:
15-
//
15+
//
1616
// The above copyright notice and this permission notice shall be included in
1717
// all copies or substantial portions of the Software.
18-
//
18+
//
1919
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2020
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2121
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -25,13 +25,16 @@
2525
// THE SOFTWARE.
2626

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

3132
namespace Pinta.Tools;
3233

3334
public abstract class BaseTransformTool : BaseTool
3435
{
36+
private readonly IWorkspaceService workspace;
37+
3538
private readonly int rotate_steps = 32;
3639
private readonly Matrix transform = CairoExtensions.CreateIdentityMatrix ();
3740
private RectangleD source_rect;
@@ -41,13 +44,23 @@ public abstract class BaseTransformTool : BaseTool
4144
private bool is_scaling = false;
4245
private bool using_mouse = false;
4346

47+
private readonly RectangleHandle rect_handle;
48+
4449
/// <summary>
4550
/// Initializes a new instance of the <see cref="BaseTransformTool"/> class.
4651
/// </summary>
4752
public BaseTransformTool (IServiceProvider services) : base (services)
4853
{
54+
workspace = services.GetService<IWorkspaceService> ();
55+
56+
rect_handle = new (workspace) {
57+
InvertIfNegative = false,
58+
Active = true
59+
};
4960
}
5061

62+
public override IEnumerable<IToolHandle> Handles => [rect_handle];
63+
5164
protected override void OnMouseDown (
5265
Document document,
5366
ToolMouseEventArgs e)
@@ -62,7 +75,7 @@ protected override void OnMouseDown (
6275

6376
if (e.MouseButton == MouseButton.Right)
6477
is_rotating = true;
65-
else if (e.IsControlPressed)
78+
else if (rect_handle.BeginDrag (e.PointDouble, document.ImageSize))
6679
is_scaling = true;
6780
else
6881
is_dragging = true;
@@ -76,54 +89,50 @@ protected override void OnMouseMove (
7689
Document document,
7790
ToolMouseEventArgs e)
7891
{
79-
if (!IsActive || !using_mouse)
92+
if (!IsActive || !using_mouse) {
93+
UpdateCursor (e.WindowPoint);
8094
return;
81-
82-
bool constrain = e.IsShiftPressed;
83-
84-
PointD center = source_rect.GetCenter ();
85-
86-
// The cursor position can be a subpixel value. Round to an integer
87-
// so that we only translate by entire pixels.
88-
// (Otherwise, blurring / anti-aliasing may be introduced)
89-
90-
double dx = Math.Floor (e.PointDouble.X - original_point.X);
91-
double dy = Math.Floor (e.PointDouble.Y - original_point.Y);
92-
93-
PointD c1 = original_point - center;
94-
PointD c2 = e.PointDouble - center;
95-
96-
RadiansAngle angle = new (Math.Atan2 (c1.Y, c1.X) - Math.Atan2 (c2.Y, c2.X));
95+
}
9796

9897
transform.InitIdentity ();
9998

10099
if (is_scaling) {
100+
// TODO - the constrain option should preserve the original aspect ratio, rather than creating a square.
101+
rect_handle.UpdateDrag (e.PointDouble, e.IsShiftPressed);
101102

102-
double sx = (c1.X + dx) / c1.X;
103-
double sy = (c1.Y + dy) / c1.Y;
104-
105-
if (constrain) {
103+
// Scale the original rectangle to fit the target rectangle.
104+
RectangleD targetRect = rect_handle.Rectangle;
105+
double sx = (source_rect.Width > 0) ? (targetRect.Width / source_rect.Width) : 0.0;
106+
double sy = (source_rect.Height > 0) ? (targetRect.Height / source_rect.Height) : 0.0;
106107

107-
double max_scale = Math.Max (Math.Abs (sx), Math.Abs (sy));
108-
109-
sx = max_scale * Math.Sign (sx);
110-
sy = max_scale * Math.Sign (sy);
111-
}
112-
113-
transform.Translate (center.X, center.Y);
108+
transform.Translate (targetRect.Left, targetRect.Top);
114109
transform.Scale (sx, sy);
115-
transform.Translate (-center.X, -center.Y);
110+
transform.Translate (-source_rect.Left, -source_rect.Top);
116111
} else if (is_rotating) {
112+
PointD center = source_rect.GetCenter ();
113+
114+
PointD c1 = original_point - center;
115+
PointD c2 = e.PointDouble - center;
116+
117+
RadiansAngle angle = new (Math.Atan2 (c1.Y, c1.X) - Math.Atan2 (c2.Y, c2.X));
117118

118-
if (constrain)
119+
if (e.IsShiftPressed)
119120
angle = Utility.GetNearestStepAngle (angle, rotate_steps);
120121

121122
transform.Translate (center.X, center.Y);
122123
transform.Rotate (-angle.Radians);
123124
transform.Translate (-center.X, -center.Y);
124125

125126
} else {
127+
// The cursor position can be a subpixel value. Round to an integer
128+
// so that we only translate by entire pixels.
129+
// (Otherwise, blurring / anti-aliasing may be introduced)
130+
double dx = Math.Floor (e.PointDouble.X - original_point.X);
131+
double dy = Math.Floor (e.PointDouble.Y - original_point.Y);
126132
transform.Translate (dx, dy);
133+
134+
// Update the rectangle handle.
135+
rect_handle.Rectangle = source_rect with { X = source_rect.X + dx, Y = source_rect.Y + dy };
127136
}
128137

129138
OnUpdateTransform (document, transform);
@@ -136,7 +145,11 @@ protected override void OnMouseUp (
136145
if (!IsActive || !using_mouse)
137146
return;
138147

148+
if (is_scaling)
149+
rect_handle.EndDrag ();
150+
139151
OnFinishTransform (document, transform);
152+
UpdateCursor (e.WindowPoint);
140153
}
141154

142155
protected override bool OnKeyDown (
@@ -213,5 +226,59 @@ protected virtual void OnFinishTransform (
213226

214227
private bool IsActive
215228
=> is_dragging || is_rotating || is_scaling;
229+
230+
protected override void OnActivated (Document? document)
231+
{
232+
base.OnActivated (document);
233+
234+
workspace.ActiveDocumentChanged += HandleActiveDocumentChanged;
235+
236+
if (document is not null)
237+
UpdateSourceRectangle (document);
238+
}
239+
240+
protected override void OnDeactivated (Document? document, BaseTool? newTool)
241+
{
242+
base.OnDeactivated (document, newTool);
243+
244+
workspace.ActiveDocumentChanged -= HandleActiveDocumentChanged;
245+
}
246+
247+
protected override void OnAfterUndo (Document document)
248+
{
249+
base.OnAfterUndo (document);
250+
UpdateSourceRectangle (document);
251+
}
252+
253+
protected override void OnAfterRedo (Document document)
254+
{
255+
base.OnAfterRedo (document);
256+
UpdateSourceRectangle (document);
257+
}
258+
259+
/// <summary>
260+
/// Update the handles whenever we switch to a new document.
261+
/// </summary>
262+
private void HandleActiveDocumentChanged (object? sender, EventArgs args)
263+
{
264+
if (!PintaCore.Workspace.HasOpenDocuments)
265+
return;
266+
267+
UpdateSourceRectangle (PintaCore.Workspace.ActiveDocument);
268+
}
269+
270+
private void UpdateSourceRectangle (Document document)
271+
{
272+
rect_handle.Rectangle = GetSourceRectangle (document);
273+
}
274+
275+
private void UpdateCursor (in PointD viewPos)
276+
{
277+
Gdk.Cursor? cursor = null;
278+
if (rect_handle.Active)
279+
cursor = rect_handle.GetCursorAtPoint (viewPos);
280+
281+
SetCursor (cursor ?? DefaultCursor);
282+
}
216283
}
217284

0 commit comments

Comments
 (0)