Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handles follow moved selection and resize on rotate, and... #802

Open
wants to merge 4 commits into
base: feature/resize-selected-pixels
Choose a base branch
from
Open
Show file tree
Hide file tree
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
5 changes: 1 addition & 4 deletions Pinta.Core/Classes/Rectangle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,7 @@ public static RectangleD FromPoints (in PointD start, in PointD end, bool invert
double x2 = Math.Max (start.X, end.X);
return new RectangleD (x1, y1, x2 - x1, y2 - y1);
} else {
return new RectangleD (start.X,
start.Y,
Math.Max (0.0, end.X - start.X),
Math.Max (0.0, end.Y - start.Y));
return new RectangleD (start.X, start.Y, end.X - start.X, end.Y - start.Y);
}
}

Expand Down
174 changes: 114 additions & 60 deletions Pinta.Tools/Handles/RectangleHandle.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Linq;
using Cairo;
using Pinta.Core;
Expand All @@ -17,6 +18,7 @@ public class RectangleHandle : IToolHandle
private readonly MoveHandle[] handles = new MoveHandle[8];
private MoveHandle? active_handle = null;
private PointD? drag_start_pos = null;
private double aspect_ratio = 1;

public RectangleHandle ()
{
Expand All @@ -33,6 +35,18 @@ public RectangleHandle ()
handle.Active = true;
}

private enum HandleIndex
{
TopLeft = 0,
BottomLeft = 1,
TopRight = 2,
BottomRight = 3,
MiddleLeft = 4,
TopMiddle = 5,
MiddleRight = 6,
BottomMiddle = 7
}

#region IToolHandle Implementation
public bool Active { get; set; }

Expand Down Expand Up @@ -82,6 +96,7 @@ public bool BeginDrag (in PointD canvas_pos, in Size image_size)
if (IsDragging)
return false;

aspect_ratio = Rectangle.Width / Rectangle.Height;
this.image_size = image_size;

PointD view_pos = PintaCore.Workspace.CanvasPointToView (canvas_pos);
Expand All @@ -99,7 +114,7 @@ public bool BeginDrag (in PointD canvas_pos, in Size image_size)
/// </summary>
/// <param name="constrain">Constrain the rectangle to be a square</param>
/// <returns>The region to redraw with InvalidateWindowRect()</returns>
public RectangleI UpdateDrag (PointD canvas_pos, bool constrain)
public RectangleI UpdateDrag (PointD canvas_pos, ConstrainType constrain = ConstrainType.None)
{
if (!IsDragging)
throw new InvalidOperationException ("Drag operation has not been started!");
Expand Down Expand Up @@ -147,6 +162,7 @@ public void EndDrag ()
var rect = Rectangle;
start_pt = rect.Location ();
end_pt = rect.EndLocation ();
Console.WriteLine($"Start: {start_pt}, End: {end_pt}");
}

/// <summary>
Expand Down Expand Up @@ -180,83 +196,121 @@ private void UpdateHandleUnderPoint (PointD view_pos)
active_handle = handles[3];
}

private void MoveActiveHandle (double x, double y, bool constrain)
private void MoveActiveHandle (double x, double y, ConstrainType constrain = ConstrainType.None)
{
// Update the rectangle's size depending on which handle was dragged.
switch (Array.IndexOf (handles, active_handle)) {
case 0:
Console.WriteLine((HandleIndex)Array.IndexOf (handles, active_handle));
switch ((HandleIndex)Array.IndexOf (handles, active_handle)) {
case HandleIndex.TopLeft:
start_pt = new (x, y);
if (constrain) {
if (end_pt.X - start_pt.X <= end_pt.Y - start_pt.Y)
start_pt = start_pt with { X = end_pt.X - end_pt.Y + start_pt.Y };
else
start_pt = start_pt with { Y = end_pt.Y - end_pt.X + start_pt.X };
}
PositionPointsFromCornerHandle (end_pt.X, end_pt.Y, ref start_pt, ref start_pt, x, y, constrain);
break;
case 1:
start_pt = start_pt with { X = x };
end_pt = end_pt with { Y = y };
if (constrain) {
if (end_pt.X - start_pt.X <= end_pt.Y - start_pt.Y)
start_pt = start_pt with { X = end_pt.X - end_pt.Y + start_pt.Y };
else
end_pt = end_pt with { Y = start_pt.Y + end_pt.X - start_pt.X };
}
break;
case 2:
end_pt = end_pt with { X = x };
case HandleIndex.TopRight:
start_pt = start_pt with { Y = y };
if (constrain) {
if (end_pt.X - start_pt.X <= end_pt.Y - start_pt.Y)
end_pt = end_pt with { X = start_pt.X + end_pt.Y - start_pt.Y };
else
start_pt = start_pt with { Y = end_pt.Y - end_pt.X + start_pt.X };
}
end_pt = end_pt with { X = x };
PositionPointsFromCornerHandle (start_pt.X, end_pt.Y, ref end_pt, ref start_pt, x, y, constrain);
break;
case 3:
case HandleIndex.BottomRight:
end_pt = new (x, y);
if (constrain) {
if (end_pt.X - start_pt.X <= end_pt.Y - start_pt.Y)
end_pt = end_pt with { X = start_pt.X + end_pt.Y - start_pt.Y };
else
end_pt = end_pt with { Y = start_pt.Y + end_pt.X - start_pt.X };
}
PositionPointsFromCornerHandle (start_pt.X, start_pt.Y, ref end_pt, ref end_pt, x, y, constrain);
break;
case 4:
case HandleIndex.BottomLeft:
start_pt = start_pt with { X = x };
if (constrain) {
var d = end_pt.X - start_pt.X;
start_pt = start_pt with { Y = (start_pt.Y + end_pt.Y - d) / 2 };
end_pt = end_pt with { Y = (start_pt.Y + end_pt.Y + d) / 2 };
}
end_pt = end_pt with { Y = y };
PositionPointsFromCornerHandle (end_pt.X, start_pt.Y, ref start_pt, ref end_pt, x, y, constrain);
break;
case 5:
start_pt = start_pt with { Y = y };
if (constrain) {
var d = end_pt.Y - start_pt.Y;
start_pt = start_pt with { X = (start_pt.X + end_pt.X - d) / 2 };
end_pt = end_pt with { X = (start_pt.X + end_pt.X + d) / 2 };
}
case HandleIndex.MiddleLeft:
start_pt = start_pt with { X = x };
PositionPointsFromLeftRightHandle (constrain);
break;
case 6:
case HandleIndex.MiddleRight:
end_pt = end_pt with { X = x };
if (constrain) {
var d = end_pt.X - start_pt.X;
start_pt = start_pt with { Y = (start_pt.Y + end_pt.Y - d) / 2 };
end_pt = end_pt with { Y = (start_pt.Y + end_pt.Y + d) / 2 };
}
PositionPointsFromLeftRightHandle (constrain);
break;
case 7:
case HandleIndex.TopMiddle:
start_pt = start_pt with { Y = y };
PositionPointsFromTopBottomHandle (constrain);
break;
case HandleIndex.BottomMiddle:
end_pt = end_pt with { Y = y };
if (constrain) {
var d = end_pt.Y - start_pt.Y;
start_pt = start_pt with { X = (start_pt.X + end_pt.X - d) / 2 };
end_pt = end_pt with { X = (start_pt.X + end_pt.X + d) / 2 };
}
PositionPointsFromTopBottomHandle (constrain);
break;
default:
throw new ArgumentOutOfRangeException (nameof (active_handle));
}

}

private void PositionPointsFromCornerHandle (double j, double k, ref PointD c, ref PointD d, double x, double y, ConstrainType constrain)
{
double aspectRatio;
if (constrain == ConstrainType.Square)
aspectRatio = 1;
else if (constrain == ConstrainType.AspectRatio)
aspectRatio = aspect_ratio;
else return;

if (x - j >= 0 && y - k >= 0) {
if (x - j > aspectRatio * (y - k))
c = c with { X = j + (y - k) * aspectRatio };
else
d = d with { Y = k + (x - j) / aspectRatio };
} else if (x - j <= 0 && y - k <= 0) {
if (x - j < aspectRatio * (y - k))
c = c with { X = j + (y - k) * aspectRatio };
else
d = d with { Y = k + (x - j) / aspectRatio };
} else if (x - j <= 0 && y - k >= 0) {
if (x - j < aspectRatio * (k - y))
c = c with { X = j + (k - y) * aspectRatio };
else
d = d with { Y = k + (j - x) / aspectRatio };
} else if (x - j >= 0 && y - k <= 0) {
if (j - x < aspectRatio * (y - k))
c = c with { X = j + (k - y) * aspectRatio };
else
d = d with { Y = k + (j - x) / aspectRatio };
}
}
private void PositionPointsFromLeftRightHandle (ConstrainType constrain)
{
double aspectRatio;
if (constrain == ConstrainType.Square)
aspectRatio = 1;
else if (constrain == ConstrainType.AspectRatio)
aspectRatio = aspect_ratio;
else return;

var d = end_pt.X - start_pt.X;
var startY = start_pt.Y;
var a = (startY + end_pt.Y - d / aspectRatio) / 2;
var b = (startY + end_pt.Y + d / aspectRatio) / 2;
Console.WriteLine($"A: {a}, B: {b}");
start_pt = start_pt with { Y = Math.Min(a, b) };
end_pt = end_pt with { Y = Math.Max(a, b) };
}

private void PositionPointsFromTopBottomHandle (ConstrainType constrain)
{
double aspectRatio;
if (constrain == ConstrainType.Square)
aspectRatio = 1;
else if (constrain == ConstrainType.AspectRatio)
aspectRatio = aspect_ratio;
else return;

var d = end_pt.Y - start_pt.Y;
var startX = start_pt.X;
var a = (startX + end_pt.X - d * aspectRatio) / 2;
var b = (startX + end_pt.X + d * aspectRatio) / 2;
start_pt = start_pt with { X = Math.Min(a, b) };
end_pt = end_pt with { X = Math.Max(a, b) };
}
}

public enum ConstrainType
{
None,
Square,
AspectRatio
}
13 changes: 9 additions & 4 deletions Pinta.Tools/Tools/BaseTransformTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using System.Linq;
using Cairo;
using Pinta.Core;
using Pinta.Tools.Handles;

namespace Pinta.Tools;

Expand All @@ -38,11 +39,12 @@ public abstract class BaseTransformTool : BaseTool
private readonly Matrix transform = CairoExtensions.CreateIdentityMatrix ();
private RectangleD source_rect;
private PointD original_point;
private PointD rect_original_point;
private bool is_dragging = false;
private bool is_rotating = false;
private bool is_scaling = false;
private bool using_mouse = false;
private readonly Handles.RectangleHandle rect_handle;
protected readonly Handles.RectangleHandle rect_handle;

public override IEnumerable<IToolHandle> Handles => Enumerable.Repeat (rect_handle, 1);

Expand All @@ -51,7 +53,7 @@ public abstract class BaseTransformTool : BaseTool
/// </summary>
public BaseTransformTool (IServiceManager services) : base (services)
{
rect_handle = new () { InvertIfNegative = false, Active = true };
rect_handle = new () { Active = true };
}

protected override void OnActivated (Document? document)
Expand All @@ -77,6 +79,7 @@ protected override void OnMouseDown (Document document, ToolMouseEventArgs e)
return;

original_point = e.PointDouble;
rect_original_point = new (rect_handle.Rectangle.X, rect_handle.Rectangle.Y);

if (!document.Workspace.PointInCanvas (e.PointDouble))
return;
Expand Down Expand Up @@ -105,8 +108,7 @@ protected override void OnMouseMove (Document document, ToolMouseEventArgs e)
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, constrain);
rect_handle.UpdateDrag (e.PointDouble, constrain ? ConstrainType.AspectRatio : ConstrainType.None);

// Scale the original rectangle to fit the target rectangle.
var target_rect = rect_handle.Rectangle;
Expand Down Expand Up @@ -134,13 +136,16 @@ protected override void OnMouseMove (Document document, ToolMouseEventArgs e)
transform.Translate (center.X, center.Y);
transform.Rotate (-angle);
transform.Translate (-center.X, -center.Y);
//TODO: the handle should rotate with the selection rather than just resizing to fit the new bounds
rect_handle.Rectangle = document.Selection.SelectionPath.GetBounds ().ToDouble ();
} 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)
var dx = Math.Floor (e.PointDouble.X - original_point.X);
var dy = Math.Floor (e.PointDouble.Y - original_point.Y);
transform.Translate (dx, dy);
rect_handle.Rectangle = new RectangleD(rect_original_point.X + dx, rect_original_point.Y + dy, rect_handle.Rectangle.Width, rect_handle.Rectangle.Height);
}

OnUpdateTransform (document, transform);
Expand Down
14 changes: 8 additions & 6 deletions Pinta.Tools/Tools/MoveSelectedTool.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
//
//
// MoveSelectedTool.cs
//
//
// Author:
// Jonathan Pobst <[email protected]>
//
//
// Copyright (c) 2010 Jonathan Pobst
//
//
// 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 Down Expand Up @@ -126,6 +126,8 @@ protected override void OnFinishTransform (Document document, Matrix transform)
if (hist != null)
document.History.PushNewItem (hist);

rect_handle.Rectangle = RectangleD.FromPoints (rect_handle.Rectangle.Location (), rect_handle.Rectangle.EndLocation ());

hist = null;
original_selection = null;
original_transform.InitIdentity ();
Expand Down
13 changes: 7 additions & 6 deletions Pinta.Tools/Tools/MoveSelectionTool.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
//
//
// MoveSelectionTool.cs
//
//
// Author:
// Jonathan Pobst <[email protected]>
//
//
// Copyright (c) 2010 Jonathan Pobst
//
//
// 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 @@ -36,6 +36,7 @@ public sealed class MoveSelectionTool : BaseTransformTool

public MoveSelectionTool (IServiceManager service) : base (service)
{
rect_handle.InvertIfNegative = true;
}

public override string Name => Translations.GetString ("Move Selection");
Expand Down
2 changes: 1 addition & 1 deletion Pinta.Tools/Tools/SelectTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ protected override void OnMouseMove (Document document, ToolMouseEventArgs e)
return;
}

var dirty = handle.UpdateDrag (e.PointDouble, e.IsShiftPressed);
var dirty = handle.UpdateDrag (e.PointDouble, e.IsShiftPressed ? ConstrainType.Square : ConstrainType.None);
PintaCore.Workspace.InvalidateWindowRect (dirty);

dirty = ReDraw (document);
Expand Down
Loading