Affinity Designer is fantastic value for money, but there's just a few things which I'd love to see to make the workflow a bit easier. I've made an art tool in the past which had a rotation & scale tool which worked like AI, and IMO it makes the workflow a whole lot snappier.
On keypress for say R or S (I'm unsure if these are bound to anything yet)
1. The currently selected tool is switched to Rotation or Scale (and probably changes the cursor to indicate it)
2. The transformation centre is set to the average location of all selected items, and is drawn as a small indicator (as is done now in some more manual modes)
On Cursor Down with the Rotation or Scale tool active
1. The original translations/rotations/scales of the selected objects are cached (rather than incremental rotations & scales, items will be reverted and updated on each cursor move)
2. The cursor location is recorded as cursorStart (how far the cursor has moved from this location defines the effect)
3. A variable hasUnsavedTransformation is set to false (until a minimum threshold has been passed, the cursor will behave differently on mouse up)
On Cursor Move while cursor is down and rotation/scale tool is active
1. Call the rotation / scale method below with the cursor position
On Mouse Up
1. If hasUnsavedTransformation is false, the transformation centre is moved to the cursor location (allowing fast positioning of rotation and scale centres)
2. If hasUnsavedTransformation is true, call the rotation / scale method below, and now consider it applied (i.e. creating an undo/redo state)
On keypress Escape
1. Restore all selected items to their cached original locations/rotations/scales
2. Release the rotation/scale tool
Scale Method:
scaleX = (cursor.x - transformCentre.x) / (cursorStart.x - transformCentre.x)
scaleY = (cursor.y - transformCentre.y) / (cursorStart.y - transformCentre.y)
threshold = 0.02
if (hasUnsavedTransformation || Math.abs(1-scaleX) > threshold || Math.abs(1-scaleY) > threshold):
Restore all selected items to their cached original locations/rotations/scales
For each selected item
location.x = (location.x - transformCentre.x) * scaleX + transformCentre.x
location.y = (location.y - transformCentre.y) * scaleY + transformCentre.y
Set hasUnsavedTransformation to true
Rotation Method:
xDiff = cursor.x - transformCentre.x
yDiff = cursor.y - transformCentre.y
rotationStart = Math.atan2(cursorStart.y - transformCentre.y, cursorStart.x - transformCentre.x) (atan2 differs a bit between languages, this is the javascript implementation)
rotation = Math.atan2(yDiff, xDiff) - rotationStart
threshold = 2 * (Math.PI/180) (i.e. 2 degrees in radians)
if (hasUnsavedTransformation || Math.abs(rotation) > threshold):
Restore all selected items to their cached original locations/rotations/scales
s = Math.sin(rotation)
c = Math.cos(rotation)
For each selected item
relativeX = location.x - transformCentre.x
relativeY = location.y - transformCentre.y
location.x = relativeX * c - relativeY * s + transformCentre.x
location.y = relativeX * s + relativeY * c + transformCentre.y
Set hasUnsavedTransformation to true