support select tool rotation
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
5078265d14
commit
d764221d63
4 changed files with 140 additions and 78 deletions
|
@ -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
|
||||
|
|
15
js/config.js
15
js/config.js
|
@ -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/"}),
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue