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

View file

@ -21,6 +21,21 @@ const config = makeReadOnly(
// Rotate Handle Distance (from selection)
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
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({
x: -this.canvas.width / 2,
y: -this.canvas.height / 2,
w: this.canvas.width,
h: this.canvas.height,
x: (this.scale.x * -this.canvas.width) / 2,
y: (this.scale.y * -this.canvas.height) / 2,
w: this.canvas.width * this.scale.x,
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 =
Math.max(
Math.abs(localc.x - localbb.tl.x),
Math.abs(localc.y - localbb.tl.y)
) <
config.handleDetectSize / 2;
(config.handleDetectSize / 2) * scale;
const ontr =
Math.max(
Math.abs(localc.x - localbb.tr.x),
Math.abs(localc.y - localbb.tr.y)
) <
config.handleDetectSize / 2;
(config.handleDetectSize / 2) * scale;
const onbl =
Math.max(
Math.abs(localc.x - localbb.bl.x),
Math.abs(localc.y - localbb.bl.y)
) <
config.handleDetectSize / 2;
(config.handleDetectSize / 2) * scale;
const onbr =
Math.max(
Math.abs(localc.x - localbb.br.x),
Math.abs(localc.y - localbb.br.y)
) <
config.handleDetectSize / 2;
(config.handleDetectSize / 2) * scale;
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
*/
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);
context.save();
@ -496,13 +514,32 @@ const _tool = {
context.lineTo(tl.x, tl.y);
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
const drawHandle = (pt, hover) => {
let hsz = config.handleDrawSize / 2;
if (hover) hsz *= config.handleDrawHoverScale;
const hm = new DOMMatrix().rotateSelf(this.rotation);
const htl = hm.transformPoint({x: -hsz, y: -hsz});
const htr = hm.transformPoint({x: hsz, y: -hsz});
const hbr = hm.transformPoint({x: hsz, y: hsz});
@ -521,7 +558,11 @@ const _tool = {
context.lineWidth = 2;
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(tr, ontr);
@ -533,10 +574,10 @@ const _tool = {
return () => {
const border = config.handleDrawSize * config.handleDrawHoverScale;
const minx = Math.min(tl.x, tr.x, bl.x, br.x) - border;
const maxx = Math.max(tl.x, tr.x, bl.x, br.x) + border;
const miny = Math.min(tl.y, tr.y, bl.y, br.y) - border;
const maxy = Math.max(tl.y, tr.y, bl.y, br.y) + 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, rh.x) + 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, rh.y) + border;
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
state.reset = (erase = false) => {
if (state.selected && !erase)
@ -129,7 +135,9 @@ const selectTransformTool = () =>
state.rotation = 0;
state.original = null;
state.moving = false;
moving = null;
scaling = null;
rotating = null;
state.redraw();
};
@ -137,11 +145,6 @@ const selectTransformTool = () =>
// Selection Handlers
const selection = _tool._draggable_selection(state);
/** @type {{selected: Point, offset: Point} | null} */
let moving = null;
/** @type {{handle: Point} | null} */
let scaling = null;
// UI Erasers
let eraseSelectedBox = () => null;
let eraseSelectedImage = () => null;
@ -191,7 +194,8 @@ const selectTransformTool = () =>
if (
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";
}
@ -286,11 +290,16 @@ const selectTransformTool = () =>
if (state.selected) {
const hoveringBox = state.selected.hoveringBox(ix, iy);
const hoveringHandle = state.selected.hoveringHandle(ix, iy);
const localc = state.selected.matrix
.inverse()
.transformPoint({x: ix, y: iy});
const hoveringHandle = state.selected.hoveringHandle(
ix,
iy,
viewport.zoom
);
const hoveringRotateHandle = state.selected.hoveringRotateHandle(
ix,
iy,
viewport.zoom
);
if (hoveringBox) {
// Start dragging
@ -327,38 +336,56 @@ const selectTransformTool = () =>
handle,
};
return;
} else if (hoveringRotateHandle) {
rotating = true;
return;
}
}
selection.dragstartcb(evn);
};
const 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};
}
}
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) {
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};
}
}
}
if (state.selected) transform(evn, x, y, sx, sy);
if (selection.exists) selection.dragcb(evn);
};
@ -406,32 +433,11 @@ const selectTransformTool = () =>
selection.deselect();
}
if (state.selected) {
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};
}
}
}
if (state.selected) transform(evn, x, y, sx, sy);
moving = null;
scaling = null;
rotating = false;
state.redraw();
};