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>
|
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
|
||||||
|
|
15
js/config.js
15
js/config.js
|
@ -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/"}),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,38 +336,56 @@ const selectTransformTool = () =>
|
||||||
handle,
|
handle,
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
|
} else if (hoveringRotateHandle) {
|
||||||
|
rotating = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selection.dragstartcb(evn);
|
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
|
// Handles left mouse drag events
|
||||||
state.dragcb = (evn) => {
|
state.dragcb = (evn) => {
|
||||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||||
|
|
||||||
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};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue