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
2525// THE SOFTWARE.
2626
2727using System ;
28+ using System . Collections . Generic ;
2829using Cairo ;
2930using Pinta . Core ;
3031
3132namespace Pinta . Tools ;
3233
3334public 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