support select tool rotation

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
Victor Seiji Hariki 2023-01-12 21:49:06 -03:00
parent 5078265d14
commit d764221d63
4 changed files with 140 additions and 78 deletions

View file

@ -355,7 +355,7 @@
type="text/javascript"></script> type="text/javascript"></script>
<!-- Configuration --> <!-- Configuration -->
<script src="js/config.js?v=664f6ee" type="text/javascript"></script> <script src="js/config.js?v=36739c9" type="text/javascript"></script>
<!-- Content --> <!-- Content -->
<script src="js/prompt.js?v=7a1c68c" type="text/javascript"></script> <script src="js/prompt.js?v=7a1c68c" type="text/javascript"></script>
@ -370,7 +370,7 @@
<!-- Load Tools --> <!-- Load Tools -->
<script <script
src="js/ui/tool/generic.js?v=9f1f84e" src="js/ui/tool/generic.js?v=f5ad9d7"
type="text/javascript"></script> type="text/javascript"></script>
<script src="js/ui/tool/dream.js?v=95c916d" type="text/javascript"></script> <script src="js/ui/tool/dream.js?v=95c916d" type="text/javascript"></script>
@ -381,7 +381,7 @@
src="js/ui/tool/colorbrush.js?v=3f8c01a" src="js/ui/tool/colorbrush.js?v=3f8c01a"
type="text/javascript"></script> type="text/javascript"></script>
<script <script
src="js/ui/tool/select.js?v=26b80dd" src="js/ui/tool/select.js?v=fae1b04"
type="text/javascript"></script> type="text/javascript"></script>
<script src="js/ui/tool/stamp.js?v=3c07ac8" type="text/javascript"></script> <script src="js/ui/tool/stamp.js?v=3c07ac8" type="text/javascript"></script>
<script <script

View file

@ -21,6 +21,21 @@ const config = makeReadOnly(
// Rotate Handle Distance (from selection) // Rotate Handle Distance (from selection)
rotateHandleDistance: 32, rotateHandleDistance: 32,
// Rotation Snapping Distance
rotationSnappingDistance: (10 * Math.PI) / 180,
// Rotation Snapping Angles
rotationSnappingAngles: [
0,
Math.PI / 4,
Math.PI / 2,
(Math.PI * 3) / 4,
Math.PI,
(Math.PI * 5) / 4,
(Math.PI * 6) / 4,
(Math.PI * 7) / 4,
Math.PI * 2,
],
// Endpoint // Endpoint
api: makeReadOnly({path: "/sdapi/v1/"}), api: makeReadOnly({path: "/sdapi/v1/"}),
}, },

View file

@ -398,39 +398,55 @@ const _tool = {
); );
} }
hoveringHandle(x, y) { hoveringRotateHandle(x, y, scale = 1) {
const localc = this.matrix.inverse().transformPoint({x, y});
const localrh = {
x: 0,
y: -this.canvas.height / 2 - config.rotateHandleDistance * scale,
};
const dx = Math.abs(localc.x - localrh.x);
const dy = Math.abs(localc.y - localrh.y);
return (
dx * dx + dy * dy <
(scale * scale * config.handleDetectSize * config.handleDetectSize) / 4
);
}
hoveringHandle(x, y, scale = 1) {
const localbb = new BoundingBox({ const localbb = new BoundingBox({
x: -this.canvas.width / 2, x: (this.scale.x * -this.canvas.width) / 2,
y: -this.canvas.height / 2, y: (this.scale.y * -this.canvas.height) / 2,
w: this.canvas.width, w: this.canvas.width * this.scale.x,
h: this.canvas.height, h: this.canvas.height * this.scale.y,
}); });
const localc = this.matrix.inverse().transformPoint({x, y}); const localc = this.rtmatrix.inverse().transformPoint({x, y});
const ontl = const ontl =
Math.max( Math.max(
Math.abs(localc.x - localbb.tl.x), Math.abs(localc.x - localbb.tl.x),
Math.abs(localc.y - localbb.tl.y) Math.abs(localc.y - localbb.tl.y)
) < ) <
config.handleDetectSize / 2; (config.handleDetectSize / 2) * scale;
const ontr = const ontr =
Math.max( Math.max(
Math.abs(localc.x - localbb.tr.x), Math.abs(localc.x - localbb.tr.x),
Math.abs(localc.y - localbb.tr.y) Math.abs(localc.y - localbb.tr.y)
) < ) <
config.handleDetectSize / 2; (config.handleDetectSize / 2) * scale;
const onbl = const onbl =
Math.max( Math.max(
Math.abs(localc.x - localbb.bl.x), Math.abs(localc.x - localbb.bl.x),
Math.abs(localc.y - localbb.bl.y) Math.abs(localc.y - localbb.bl.y)
) < ) <
config.handleDetectSize / 2; (config.handleDetectSize / 2) * scale;
const onbr = const onbr =
Math.max( Math.max(
Math.abs(localc.x - localbb.br.x), Math.abs(localc.x - localbb.br.x),
Math.abs(localc.y - localbb.br.y) Math.abs(localc.y - localbb.br.y)
) < ) <
config.handleDetectSize / 2; (config.handleDetectSize / 2) * scale;
return {onHandle: ontl || ontr || onbl || onbr, ontl, ontr, onbl, onbr}; return {onHandle: ontl || ontr || onbl || onbr, ontl, ontr, onbl, onbr};
} }
@ -459,6 +475,8 @@ const _tool = {
* @param {DOMMatrix} transform A transformation matrix to transform the position by * @param {DOMMatrix} transform A transformation matrix to transform the position by
*/ */
drawBox(context, cursor, transform = new DOMMatrix()) { drawBox(context, cursor, transform = new DOMMatrix()) {
const drawscale =
1 / Math.sqrt(transform.a * transform.a + transform.b * transform.b);
const m = transform.multiply(this.matrix); const m = transform.multiply(this.matrix);
context.save(); context.save();
@ -496,13 +514,32 @@ const _tool = {
context.lineTo(tl.x, tl.y); context.lineTo(tl.x, tl.y);
context.stroke(); context.stroke();
// Draw rotation handle
context.setLineDash([]);
const hm = new DOMMatrix().rotateSelf((this.rotation * 180) / Math.PI);
const tm = m.transformPoint({x: 0, y: -this.canvas.height / 2});
const rho = hm.transformPoint({x: 0, y: -config.rotateHandleDistance});
const rh = {x: tm.x + rho.x, y: tm.y + rho.y};
let handleRadius = config.handleDrawSize / 2;
if (this.hoveringRotateHandle(cursor.x, cursor.y, drawscale))
handleRadius *= config.handleDrawHoverScale;
context.beginPath();
context.moveTo(tm.x, tm.y);
context.lineTo(rh.x, rh.y);
context.stroke();
context.beginPath();
context.arc(rh.x, rh.y, handleRadius, 0, 2 * Math.PI);
context.stroke();
// Draw handles // Draw handles
const drawHandle = (pt, hover) => { const drawHandle = (pt, hover) => {
let hsz = config.handleDrawSize / 2; let hsz = config.handleDrawSize / 2;
if (hover) hsz *= config.handleDrawHoverScale; if (hover) hsz *= config.handleDrawHoverScale;
const hm = new DOMMatrix().rotateSelf(this.rotation);
const htl = hm.transformPoint({x: -hsz, y: -hsz}); const htl = hm.transformPoint({x: -hsz, y: -hsz});
const htr = hm.transformPoint({x: hsz, y: -hsz}); const htr = hm.transformPoint({x: hsz, y: -hsz});
const hbr = hm.transformPoint({x: hsz, y: hsz}); const hbr = hm.transformPoint({x: hsz, y: hsz});
@ -521,7 +558,11 @@ const _tool = {
context.lineWidth = 2; context.lineWidth = 2;
context.setLineDash([]); context.setLineDash([]);
const {ontl, ontr, onbl, onbr} = this.hoveringHandle(cursor.x, cursor.y); const {ontl, ontr, onbl, onbr} = this.hoveringHandle(
cursor.x,
cursor.y,
drawscale
);
drawHandle(tl, ontl); drawHandle(tl, ontl);
drawHandle(tr, ontr); drawHandle(tr, ontr);
@ -533,10 +574,10 @@ const _tool = {
return () => { return () => {
const border = config.handleDrawSize * config.handleDrawHoverScale; const border = config.handleDrawSize * config.handleDrawHoverScale;
const minx = Math.min(tl.x, tr.x, bl.x, br.x) - border; const minx = Math.min(tl.x, tr.x, bl.x, br.x, rh.x) - border;
const maxx = Math.max(tl.x, tr.x, bl.x, br.x) + border; const maxx = Math.max(tl.x, tr.x, bl.x, br.x, rh.x) + border;
const miny = Math.min(tl.y, tr.y, bl.y, br.y) - border; const miny = Math.min(tl.y, tr.y, bl.y, br.y, rh.y) - border;
const maxy = Math.max(tl.y, tr.y, bl.y, br.y) + border; const maxy = Math.max(tl.y, tr.y, bl.y, br.y, rh.y) + border;
context.clearRect(minx, miny, maxx - minx, maxy - miny); context.clearRect(minx, miny, maxx - minx, maxy - miny);
}; };

View file

@ -111,6 +111,12 @@ const selectTransformTool = () =>
} }
}; };
/** @type {{selected: Point, offset: Point} | null} */
let moving = null;
/** @type {{handle: Point} | null} */
let scaling = null;
let rotating = false;
// Clears selection and make things right // Clears selection and make things right
state.reset = (erase = false) => { state.reset = (erase = false) => {
if (state.selected && !erase) if (state.selected && !erase)
@ -129,7 +135,9 @@ const selectTransformTool = () =>
state.rotation = 0; state.rotation = 0;
state.original = null; state.original = null;
state.moving = false; moving = null;
scaling = null;
rotating = null;
state.redraw(); state.redraw();
}; };
@ -137,11 +145,6 @@ const selectTransformTool = () =>
// Selection Handlers // Selection Handlers
const selection = _tool._draggable_selection(state); const selection = _tool._draggable_selection(state);
/** @type {{selected: Point, offset: Point} | null} */
let moving = null;
/** @type {{handle: Point} | null} */
let scaling = null;
// UI Erasers // UI Erasers
let eraseSelectedBox = () => null; let eraseSelectedBox = () => null;
let eraseSelectedImage = () => null; let eraseSelectedImage = () => null;
@ -191,7 +194,8 @@ const selectTransformTool = () =>
if ( if (
state.selected.hoveringBox(x, y) || state.selected.hoveringBox(x, y) ||
state.selected.hoveringHandle(x, y).onHandle state.selected.hoveringHandle(x, y, viewport.zoom).onHandle ||
state.selected.hoveringRotateHandle(x, y, viewport.zoom)
) { ) {
imageCollection.inputElement.style.cursor = "pointer"; imageCollection.inputElement.style.cursor = "pointer";
} }
@ -286,11 +290,16 @@ const selectTransformTool = () =>
if (state.selected) { if (state.selected) {
const hoveringBox = state.selected.hoveringBox(ix, iy); const hoveringBox = state.selected.hoveringBox(ix, iy);
const hoveringHandle = state.selected.hoveringHandle(ix, iy); const hoveringHandle = state.selected.hoveringHandle(
ix,
const localc = state.selected.matrix iy,
.inverse() viewport.zoom
.transformPoint({x: ix, y: iy}); );
const hoveringRotateHandle = state.selected.hoveringRotateHandle(
ix,
iy,
viewport.zoom
);
if (hoveringBox) { if (hoveringBox) {
// Start dragging // Start dragging
@ -327,16 +336,15 @@ const selectTransformTool = () =>
handle, handle,
}; };
return; return;
} else if (hoveringRotateHandle) {
rotating = true;
return;
} }
} }
selection.dragstartcb(evn); selection.dragstartcb(evn);
}; };
// Handles left mouse drag events const transform = (evn, x, y, sx, sy) => {
state.dragcb = (evn) => {
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
if (state.selected) {
if (moving) { if (moving) {
state.selected.position = { state.selected.position = {
x: sx - moving.offset.x, x: sx - moving.offset.x,
@ -358,7 +366,26 @@ const selectTransformTool = () =>
state.selected.scale = {x: scale, y: scale}; state.selected.scale = {x: scale, y: scale};
} }
} }
if (rotating) {
const center = state.selected.matrix.transformPoint({x: 0, y: 0});
let angle = Math.atan2(x - center.x, center.y - y);
if (evn.evn.shiftKey)
angle =
config.rotationSnappingAngles.find(
(v) => Math.abs(v - angle) < config.rotationSnappingDistance
) || angle;
state.selected.rotation = angle;
} }
};
// Handles left mouse drag events
state.dragcb = (evn) => {
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
if (state.selected) transform(evn, x, y, sx, sy);
if (selection.exists) selection.dragcb(evn); if (selection.exists) selection.dragcb(evn);
}; };
@ -406,32 +433,11 @@ const selectTransformTool = () =>
selection.deselect(); selection.deselect();
} }
if (state.selected) { if (state.selected) transform(evn, x, y, sx, sy);
if (moving) {
state.selected.position = {
x: sx - moving.offset.x,
y: sy - moving.offset.y,
};
}
if (scaling) {
/** @type {DOMMatrix} */
const m = state.selected.rtmatrix.invertSelf();
const lscursor = m.transformPoint({x: sx, y: sy});
const xs = lscursor.x / scaling.handle.x;
const xy = lscursor.y / scaling.handle.y;
if (!state.keepAspectRatio) state.selected.scale = {x: xs, y: xy};
else {
const scale = Math.max(xs, xy);
state.selected.scale = {x: scale, y: scale};
}
}
}
moving = null; moving = null;
scaling = null; scaling = null;
rotating = false;
state.redraw(); state.redraw();
}; };