Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 104 additions & 37 deletions Pinta.Tools/Tools/BaseTransformTool.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -25,13 +25,16 @@
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using Cairo;
using Pinta.Core;

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;
Expand All @@ -41,13 +44,23 @@ public abstract class BaseTransformTool : BaseTool
private bool is_scaling = false;
private bool using_mouse = false;

private readonly RectangleHandle rect_handle;

/// <summary>
/// Initializes a new instance of the <see cref="BaseTransformTool"/> class.
/// </summary>
public BaseTransformTool (IServiceProvider services) : base (services)
{
workspace = services.GetService<IWorkspaceService> ();

rect_handle = new (workspace) {
InvertIfNegative = false,
Active = true
};
}

public override IEnumerable<IToolHandle> Handles => [rect_handle];

protected override void OnMouseDown (
Document document,
ToolMouseEventArgs e)
Expand All @@ -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;
Expand All @@ -76,54 +89,50 @@ 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);
transform.Rotate (-angle.Radians);
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);
Expand All @@ -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 (
Expand Down Expand Up @@ -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);
}

/// <summary>
/// Update the handles whenever we switch to a new document.
/// </summary>
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);
}
}

Loading