diff --git a/Pinta.Tools/Tools/BaseTransformTool.cs b/Pinta.Tools/Tools/BaseTransformTool.cs
index 10b70888e9..c115298ee6 100644
--- a/Pinta.Tools/Tools/BaseTransformTool.cs
+++ b/Pinta.Tools/Tools/BaseTransformTool.cs
@@ -1,21 +1,21 @@
-//
+//
// BaseTransformTool.cs
-//
+//
// Author:
// Volodymyr <${AuthorEmail}>
-//
+//
// Copyright (c) 2012 Volodymyr
-//
+//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
-//
+//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
-//
+//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -25,6 +25,7 @@
// THE SOFTWARE.
using System;
+using System.Collections.Generic;
using Cairo;
using Pinta.Core;
@@ -32,6 +33,8 @@ namespace Pinta.Tools;
public abstract class BaseTransformTool : BaseTool
{
+ private readonly IWorkspaceService workspace;
+
private readonly int rotate_steps = 32;
private readonly Matrix transform = CairoExtensions.CreateIdentityMatrix ();
private RectangleD source_rect;
@@ -41,13 +44,23 @@ public abstract class BaseTransformTool : BaseTool
private bool is_scaling = false;
private bool using_mouse = false;
+ private readonly RectangleHandle rect_handle;
+
///
/// Initializes a new instance of the class.
///
public BaseTransformTool (IServiceProvider services) : base (services)
{
+ workspace = services.GetService ();
+
+ rect_handle = new (workspace) {
+ InvertIfNegative = false,
+ Active = true
+ };
}
+ public override IEnumerable Handles => [rect_handle];
+
protected override void OnMouseDown (
Document document,
ToolMouseEventArgs e)
@@ -62,7 +75,7 @@ protected override void OnMouseDown (
if (e.MouseButton == MouseButton.Right)
is_rotating = true;
- else if (e.IsControlPressed)
+ else if (rect_handle.BeginDrag (e.PointDouble, document.ImageSize))
is_scaling = true;
else
is_dragging = true;
@@ -76,46 +89,34 @@ protected override void OnMouseMove (
Document document,
ToolMouseEventArgs e)
{
- if (!IsActive || !using_mouse)
+ if (!IsActive || !using_mouse) {
+ UpdateCursor (e.WindowPoint);
return;
-
- bool constrain = e.IsShiftPressed;
-
- PointD center = source_rect.GetCenter ();
-
- // The cursor position can be a subpixel value. Round to an integer
- // so that we only translate by entire pixels.
- // (Otherwise, blurring / anti-aliasing may be introduced)
-
- double dx = Math.Floor (e.PointDouble.X - original_point.X);
- double dy = Math.Floor (e.PointDouble.Y - original_point.Y);
-
- PointD c1 = original_point - center;
- PointD c2 = e.PointDouble - center;
-
- RadiansAngle angle = new (Math.Atan2 (c1.Y, c1.X) - Math.Atan2 (c2.Y, c2.X));
+ }
transform.InitIdentity ();
if (is_scaling) {
+ // TODO - the constrain option should preserve the original aspect ratio, rather than creating a square.
+ rect_handle.UpdateDrag (e.PointDouble, e.IsShiftPressed);
- double sx = (c1.X + dx) / c1.X;
- double sy = (c1.Y + dy) / c1.Y;
-
- if (constrain) {
+ // Scale the original rectangle to fit the target rectangle.
+ RectangleD targetRect = rect_handle.Rectangle;
+ double sx = (source_rect.Width > 0) ? (targetRect.Width / source_rect.Width) : 0.0;
+ double sy = (source_rect.Height > 0) ? (targetRect.Height / source_rect.Height) : 0.0;
- double max_scale = Math.Max (Math.Abs (sx), Math.Abs (sy));
-
- sx = max_scale * Math.Sign (sx);
- sy = max_scale * Math.Sign (sy);
- }
-
- transform.Translate (center.X, center.Y);
+ transform.Translate (targetRect.Left, targetRect.Top);
transform.Scale (sx, sy);
- transform.Translate (-center.X, -center.Y);
+ transform.Translate (-source_rect.Left, -source_rect.Top);
} else if (is_rotating) {
+ PointD center = source_rect.GetCenter ();
+
+ PointD c1 = original_point - center;
+ PointD c2 = e.PointDouble - center;
+
+ RadiansAngle angle = new (Math.Atan2 (c1.Y, c1.X) - Math.Atan2 (c2.Y, c2.X));
- if (constrain)
+ if (e.IsShiftPressed)
angle = Utility.GetNearestStepAngle (angle, rotate_steps);
transform.Translate (center.X, center.Y);
@@ -123,7 +124,15 @@ protected override void OnMouseMove (
transform.Translate (-center.X, -center.Y);
} else {
+ // The cursor position can be a subpixel value. Round to an integer
+ // so that we only translate by entire pixels.
+ // (Otherwise, blurring / anti-aliasing may be introduced)
+ double dx = Math.Floor (e.PointDouble.X - original_point.X);
+ double dy = Math.Floor (e.PointDouble.Y - original_point.Y);
transform.Translate (dx, dy);
+
+ // Update the rectangle handle.
+ rect_handle.Rectangle = source_rect with { X = source_rect.X + dx, Y = source_rect.Y + dy };
}
OnUpdateTransform (document, transform);
@@ -136,7 +145,11 @@ protected override void OnMouseUp (
if (!IsActive || !using_mouse)
return;
+ if (is_scaling)
+ rect_handle.EndDrag ();
+
OnFinishTransform (document, transform);
+ UpdateCursor (e.WindowPoint);
}
protected override bool OnKeyDown (
@@ -213,5 +226,59 @@ protected virtual void OnFinishTransform (
private bool IsActive
=> is_dragging || is_rotating || is_scaling;
+
+ protected override void OnActivated (Document? document)
+ {
+ base.OnActivated (document);
+
+ workspace.ActiveDocumentChanged += HandleActiveDocumentChanged;
+
+ if (document is not null)
+ UpdateSourceRectangle (document);
+ }
+
+ protected override void OnDeactivated (Document? document, BaseTool? newTool)
+ {
+ base.OnDeactivated (document, newTool);
+
+ workspace.ActiveDocumentChanged -= HandleActiveDocumentChanged;
+ }
+
+ protected override void OnAfterUndo (Document document)
+ {
+ base.OnAfterUndo (document);
+ UpdateSourceRectangle (document);
+ }
+
+ protected override void OnAfterRedo (Document document)
+ {
+ base.OnAfterRedo (document);
+ UpdateSourceRectangle (document);
+ }
+
+ ///
+ /// Update the handles whenever we switch to a new document.
+ ///
+ private void HandleActiveDocumentChanged (object? sender, EventArgs args)
+ {
+ if (!PintaCore.Workspace.HasOpenDocuments)
+ return;
+
+ UpdateSourceRectangle (PintaCore.Workspace.ActiveDocument);
+ }
+
+ private void UpdateSourceRectangle (Document document)
+ {
+ rect_handle.Rectangle = GetSourceRectangle (document);
+ }
+
+ private void UpdateCursor (in PointD viewPos)
+ {
+ Gdk.Cursor? cursor = null;
+ if (rect_handle.Active)
+ cursor = rect_handle.GetCursorAtPoint (viewPos);
+
+ SetCursor (cursor ?? DefaultCursor);
+ }
}