commit
b038059ffb
10 changed files with 1275 additions and 364 deletions
|
@ -325,6 +325,8 @@
|
|||
<script src="js/ui/floating/layers.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Load Tools -->
|
||||
<script src="js/ui/tool/generic.js" type="text/javascript"></script>
|
||||
|
||||
<script src="js/ui/tool/dream.js" type="text/javascript"></script>
|
||||
<script src="js/ui/tool/maskbrush.js" type="text/javascript"></script>
|
||||
<script src="js/ui/tool/colorbrush.js" type="text/javascript"></script>
|
||||
|
|
26
js/index.js
26
js/index.js
|
@ -890,30 +890,22 @@ async function upscaleAndDownload() {
|
|||
|
||||
function loadSettings() {
|
||||
// set default values if not set
|
||||
var _prompt =
|
||||
localStorage.getItem("prompt") == null
|
||||
? "ocean floor scientific expedition, underwater wildlife"
|
||||
: localStorage.getItem("prompt");
|
||||
var _negprompt =
|
||||
localStorage.getItem("neg_prompt") == null
|
||||
? "people, person, humans, human, divers, diver, glitch, error, text, watermark, bad quality, blurry"
|
||||
: localStorage.getItem("neg_prompt");
|
||||
var _mask_blur =
|
||||
localStorage.getItem("mask_blur") == null
|
||||
? 0
|
||||
: localStorage.getItem("mask_blur");
|
||||
var _seed =
|
||||
localStorage.getItem("seed") == null ? -1 : localStorage.getItem("seed");
|
||||
var _enable_hr = Boolean(
|
||||
localStorage.getItem("enable_hr") == (null || "false")
|
||||
|
||||
let _enable_hr =
|
||||
localStorage.getItem("enable_hr") === null
|
||||
? false
|
||||
: localStorage.getItem("enable_hr")
|
||||
);
|
||||
var _sync_cursor_size = Boolean(
|
||||
localStorage.getItem("sync_cursor_size") == (null || "false")
|
||||
? false
|
||||
: localStorage.getItem("sync_cursor_size")
|
||||
);
|
||||
: localStorage.getItem("enable_hr") === "true";
|
||||
|
||||
let _sync_cursor_size =
|
||||
localStorage.getItem("sync_cursor_size") === null
|
||||
? true
|
||||
: localStorage.getItem("sync_cursor_size") === "true";
|
||||
|
||||
// set the values into the UI
|
||||
document.getElementById("maskBlur").value = Number(_mask_blur);
|
||||
|
|
|
@ -126,7 +126,10 @@ const viewport = {
|
|||
return (window.innerHeight * 1) / this.zoom;
|
||||
},
|
||||
viewToCanvas(x, y) {
|
||||
return {x, y};
|
||||
return {
|
||||
x: this.cx + this.w * (x / window.innerWidth - 0.5),
|
||||
y: this.cy + this.h * (y / window.innerHeight - 0.5),
|
||||
};
|
||||
},
|
||||
canvasToView(x, y) {
|
||||
return {
|
||||
|
@ -185,7 +188,7 @@ mouse.listen.window.onwheel.on((evn) => {
|
|||
});
|
||||
|
||||
mouse.listen.window.btn.middle.onpaintstart.on((evn) => {
|
||||
worldInit = {x: viewport.cx, y: viewport.cy};
|
||||
if (evn.evn.ctrlKey) worldInit = {x: viewport.cx, y: viewport.cy};
|
||||
});
|
||||
|
||||
mouse.listen.window.btn.middle.onpaint.on((evn) => {
|
||||
|
|
|
@ -86,8 +86,8 @@
|
|||
*
|
||||
* @typedef MouseCoordContext
|
||||
* @property {{[key: string]: MouseCoordContextDragInfo}} dragging Information about mouse button drags
|
||||
* @property {{x: number, y: number}} prev Previous mouse position
|
||||
* @property {{x: number, y: number}} pos Current mouse position
|
||||
* @property {Point} prev Previous mouse position
|
||||
* @property {Point} pos Current mouse position
|
||||
*/
|
||||
|
||||
/* Here are keyboard-related types */
|
||||
|
|
|
@ -235,7 +235,7 @@ function createAutoComplete(name, wrapper, options = {}) {
|
|||
onchange: new Observer(),
|
||||
|
||||
get value() {
|
||||
const v = this._selectedOptions.map((opt) => opt.value);
|
||||
const v = Array.from(this._selectedOptions).map((opt) => opt.value);
|
||||
return options.multiple ? v : v[0];
|
||||
},
|
||||
set value(values) {
|
||||
|
|
134
js/lib/util.js
134
js/lib/util.js
|
@ -1,17 +1,58 @@
|
|||
/**
|
||||
* Some type definitions before the actual code
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a simple bounding box
|
||||
* Simple Point Coordinate
|
||||
*
|
||||
* @typedef BoundingBox
|
||||
* @type {Object}
|
||||
* @property {number} x - Leftmost coordinate of the box
|
||||
* @property {number} y - Topmost coordinate of the box
|
||||
* @property {number} w - The bounding box Width
|
||||
* @property {number} h - The bounding box Height
|
||||
* @typedef Point
|
||||
* @property {number} x - x coordinate
|
||||
* @property {number} y - y coordinate
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a simple bouding box
|
||||
*/
|
||||
class BoundingBox {
|
||||
x = 0;
|
||||
y = 0;
|
||||
w = 0;
|
||||
h = 0;
|
||||
|
||||
constructor({x, y, w, h} = {x: 0, y: 0, w: 0, h: 0}) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
contains(x, y) {
|
||||
return (
|
||||
this.x < x && this.y < y && x < this.x + this.w && y < this.y + this.h
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets bounding box from two points
|
||||
*
|
||||
* @param {Point} start Coordinate
|
||||
* @param {Point} end
|
||||
*/
|
||||
static fromStartEnd(start, end) {
|
||||
const minx = Math.min(start.x, end.x);
|
||||
const miny = Math.min(start.y, end.y);
|
||||
const maxx = Math.max(start.x, end.x);
|
||||
const maxy = Math.max(start.y, end.y);
|
||||
|
||||
return new BoundingBox({
|
||||
x: minx,
|
||||
y: miny,
|
||||
w: maxx - minx,
|
||||
h: maxy - miny,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple implementation of the Observer programming pattern
|
||||
* @template [T=any] Message type
|
||||
|
@ -19,28 +60,34 @@
|
|||
class Observer {
|
||||
/**
|
||||
* List of handlers
|
||||
* @type {Set<(msg: T) => void | Promise<void>>}
|
||||
* @type {Array<{handler: (msg: T) => void | Promise<void>, priority: number}>}
|
||||
*/
|
||||
_handlers = new Set();
|
||||
_handlers = [];
|
||||
|
||||
/**
|
||||
* Adds a observer to the events
|
||||
*
|
||||
* @param {(msg: T) => void | Promise<void>} callback The function to run when receiving a message
|
||||
* @returns {(msg:T) => void | Promise<void>} The callback we received
|
||||
* @param {(msg: T, state?: any) => void | Promise<void>} callback The function to run when receiving a message
|
||||
* @param {number} priority The priority level of the observer
|
||||
* @param {boolean} wait If the handler must be waited for before continuing
|
||||
* @returns {(msg:T, state?: any) => void | Promise<void>} The callback we received
|
||||
*/
|
||||
on(callback) {
|
||||
this._handlers.add(callback);
|
||||
on(callback, priority = 0, wait = false) {
|
||||
this._handlers.push({handler: callback, priority, wait});
|
||||
this._handlers.sort((a, b) => b.priority - a.priority);
|
||||
return callback;
|
||||
}
|
||||
/**
|
||||
* Removes a observer
|
||||
*
|
||||
* @param {(msg: T) => void | Promise<void>} callback The function used to register the callback
|
||||
* @param {(msg: T, state?: any) => void | Promise<void>} callback The function used to register the callback
|
||||
* @returns {boolean} Whether the handler existed
|
||||
*/
|
||||
clear(callback) {
|
||||
return this._handlers.delete(callback);
|
||||
const index = this._handlers.findIndex((v) => v.handler === callback);
|
||||
if (index === -1) return false;
|
||||
this._handlers.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Sends a message to all observers
|
||||
|
@ -48,16 +95,23 @@ class Observer {
|
|||
* @param {T} msg The message to send to the observers
|
||||
*/
|
||||
async emit(msg) {
|
||||
return Promise.all(
|
||||
Array.from(this._handlers).map(async (handler) => {
|
||||
const state = {};
|
||||
const promises = [];
|
||||
for (const {handler, wait} of this._handlers) {
|
||||
const run = async () => {
|
||||
try {
|
||||
await handler(msg);
|
||||
await handler(msg, state);
|
||||
} catch (e) {
|
||||
console.warn("Observer failed to run handler");
|
||||
console.warn(e);
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
if (wait) await run();
|
||||
else promises.push(run());
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,12 +265,12 @@ function getBoundingBox(cx, cy, w, h, gridSnap = null, offset = 0) {
|
|||
box.x = Math.round(offs.x + cx);
|
||||
box.y = Math.round(offs.y + cy);
|
||||
|
||||
return {
|
||||
return new BoundingBox({
|
||||
x: Math.floor(box.x - w / 2),
|
||||
y: Math.floor(box.y - h / 2),
|
||||
w: Math.round(w),
|
||||
h: Math.round(h),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
class NoContentError extends Error {}
|
||||
|
@ -236,7 +290,7 @@ function cropCanvas(sourceCanvas, options = {}) {
|
|||
const h = sourceCanvas.height;
|
||||
var imageData = sourceCanvas.getContext("2d").getImageData(0, 0, w, h);
|
||||
/** @type {BoundingBox} */
|
||||
const bb = {x: 0, y: 0, w: 0, h: 0};
|
||||
const bb = new BoundingBox();
|
||||
|
||||
let minx = w;
|
||||
let maxx = -1;
|
||||
|
@ -307,7 +361,37 @@ function downloadCanvas(options = {}) {
|
|||
? cropCanvas(options.canvas).canvas
|
||||
: options.canvas;
|
||||
if (croppedCanvas != null) {
|
||||
link.href = croppedCanvas.toDataURL("image/png");
|
||||
link.click();
|
||||
croppedCanvas.toBlob((blob) => {
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an element in a location
|
||||
* @param {string} type Element Tag
|
||||
* @param {number} x X coordinate of the element
|
||||
* @param {number} y Y coordinate of the element
|
||||
* @param {{x: number y: offset}} offset Offset to apply to the element
|
||||
* @returns
|
||||
*/
|
||||
const makeElement = (
|
||||
type,
|
||||
x,
|
||||
y,
|
||||
offset = {
|
||||
x: -imageCollection.inputOffset.x,
|
||||
y: -imageCollection.inputOffset.y,
|
||||
}
|
||||
) => {
|
||||
const el = document.createElement(type);
|
||||
el.style.position = "absolute";
|
||||
el.style.left = `${x + offset.x}px`;
|
||||
el.style.top = `${y + offset.y}px`;
|
||||
|
||||
// We can use the input element to add interactible html elements in the world
|
||||
imageCollection.inputElement.appendChild(el);
|
||||
|
||||
return el;
|
||||
};
|
||||
|
|
|
@ -76,9 +76,12 @@ async function getStyles() {
|
|||
};
|
||||
|
||||
// Load from local storage if set
|
||||
const promptDefaultValue = localStorage.getItem("prompt") || defaultPrompt;
|
||||
const storedPrompt = localStorage.getItem("prompt");
|
||||
const storedNeg = localStorage.getItem("neg_prompt");
|
||||
const promptDefaultValue =
|
||||
storedPrompt === null ? defaultPrompt : storedPrompt;
|
||||
const negativePromptDefaultValue =
|
||||
localStorage.getItem("neg_prompt") || defaultNegativePrompt;
|
||||
storedNeg === null ? defaultNegativePrompt : storedNeg;
|
||||
|
||||
promptEl.value = promptEl.title = promptDefaultValue;
|
||||
negativePromptEl.value = negativePromptEl.title = negativePromptDefaultValue;
|
||||
|
|
1109
js/ui/tool/dream.js
1109
js/ui/tool/dream.js
File diff suppressed because it is too large
Load diff
276
js/ui/tool/generic.js
Normal file
276
js/ui/tool/generic.js
Normal file
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* File to add generic rendering functions and shared utilities
|
||||
*/
|
||||
|
||||
const _tool = {
|
||||
/**
|
||||
* Draws a reticle used for image generation
|
||||
*
|
||||
* @param {BoundingBox} bb The bounding box of the reticle (world space)
|
||||
* @param {string} tool Name of the tool to diplay
|
||||
* @param {{w: number, h: number}} resolution Resolution of generation to display
|
||||
* @param {object} style Styles to use for rendering the reticle
|
||||
* @param {string} [style.sizeTextStyle = "#FFF5"] Style of the text for diplaying the bounding box size.
|
||||
* @param {string} [style.genSizeTextStyle = "#FFF5"] Style of the text for diplaying generation size
|
||||
* @param {string} [style.toolTextStyle = "#FFF5"] Style of the text for the tool name
|
||||
* @param {number} [style.reticleWidth = 1] Width of the line of the reticle
|
||||
* @param {string} [style.reticleStyle = "#FFF"] Style of the line of the reticle
|
||||
*
|
||||
* @returns A function that erases this reticle drawing
|
||||
*/
|
||||
_reticle_draw(bb, tool, resolution, style = {}) {
|
||||
defaultOpt(style, {
|
||||
sizeTextStyle: "#FFF5",
|
||||
genSizeTextStyle: "#FFF5",
|
||||
toolTextStyle: "#FFF5",
|
||||
reticleWidth: 1,
|
||||
reticleStyle: "#FFF",
|
||||
});
|
||||
|
||||
const bbvp = {
|
||||
...viewport.canvasToView(bb.x, bb.y),
|
||||
w: viewport.zoom * bb.w,
|
||||
h: viewport.zoom * bb.h,
|
||||
};
|
||||
|
||||
uiCtx.save();
|
||||
|
||||
// draw targeting square reticle thingy cursor
|
||||
uiCtx.lineWidth = style.reticleWidth;
|
||||
uiCtx.strokeStyle = style.reticleStyle;
|
||||
uiCtx.strokeRect(bbvp.x, bbvp.y, bbvp.w, bbvp.h); //origin is middle of the frame
|
||||
|
||||
uiCtx.font = `bold 20px Open Sans`;
|
||||
|
||||
// Draw Tool Name
|
||||
if (bb.h > 40) {
|
||||
const xshrink = Math.min(
|
||||
1,
|
||||
(bbvp.w - 20) / uiCtx.measureText(tool).width
|
||||
);
|
||||
|
||||
uiCtx.font = `bold ${20 * xshrink}px Open Sans`;
|
||||
|
||||
uiCtx.textAlign = "left";
|
||||
uiCtx.fillStyle = style.toolTextStyle;
|
||||
uiCtx.fillText(tool, bbvp.x + 10, bbvp.y + 10 + 20 * xshrink, bb.w);
|
||||
}
|
||||
|
||||
// Draw width and height
|
||||
{
|
||||
// Render Cursor Width
|
||||
uiCtx.textAlign = "center";
|
||||
uiCtx.fillStyle = style.sizeTextStyle;
|
||||
uiCtx.translate(bbvp.x + bbvp.w / 2, bbvp.y + bbvp.h / 2);
|
||||
const xshrink = Math.min(
|
||||
1,
|
||||
(bbvp.w - 30) / uiCtx.measureText(`${bb.w}px`).width
|
||||
);
|
||||
const yshrink = Math.min(
|
||||
1,
|
||||
(bbvp.h - 30) / uiCtx.measureText(`${bb.h}px`).width
|
||||
);
|
||||
uiCtx.font = `bold ${20 * xshrink}px Open Sans`;
|
||||
uiCtx.fillText(`${bb.w}px`, 0, bbvp.h / 2 - 10 * xshrink, bb.w);
|
||||
|
||||
// Render Generation Width
|
||||
uiCtx.fillStyle = style.genSizeTextStyle;
|
||||
uiCtx.font = `bold ${10 * xshrink}px Open Sans`;
|
||||
if (bb.w !== resolution.w)
|
||||
uiCtx.fillText(`${resolution.w}px`, 0, bbvp.h / 2 - 30 * xshrink, bb.h);
|
||||
|
||||
// Render Cursor Height
|
||||
uiCtx.rotate(-Math.PI / 2);
|
||||
uiCtx.fillStyle = style.sizeTextStyle;
|
||||
uiCtx.font = `bold ${20 * yshrink}px Open Sans`;
|
||||
uiCtx.fillText(`${bb.h}px`, 0, bbvp.w / 2 - 10 * yshrink, bb.h);
|
||||
|
||||
// Render Generation Height
|
||||
uiCtx.fillStyle = style.genSizeTextStyle;
|
||||
uiCtx.font = `bold ${10 * yshrink}px Open Sans`;
|
||||
if (bb.h !== resolution.h)
|
||||
uiCtx.fillText(`${resolution.h}px`, 0, bbvp.w / 2 - 30 * xshrink, bb.h);
|
||||
|
||||
uiCtx.restore();
|
||||
}
|
||||
|
||||
return () => {
|
||||
uiCtx.save();
|
||||
|
||||
uiCtx.clearRect(bbvp.x - 64, bbvp.y - 64, bbvp.w + 128, bbvp.h + 128);
|
||||
|
||||
uiCtx.restore();
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Draws a generic crosshair cursor at the specified location
|
||||
*
|
||||
* @param {number} x X world coordinate of the cursor
|
||||
* @param {number} y Y world coordinate of the cursor
|
||||
* @param {object} style Style of the lines of the cursor
|
||||
* @param {string} [style.width = 3] Line width of the lines of the cursor
|
||||
* @param {string} [style.style = "#FFF5"] Stroke style of the lines of the cursor
|
||||
*
|
||||
* @returns A function that erases this cursor drawing
|
||||
*/
|
||||
_cursor_draw(x, y, style = {}) {
|
||||
defaultOpt(style, {
|
||||
width: 3,
|
||||
style: "#FFF5",
|
||||
});
|
||||
const vpc = viewport.canvasToView(x, y);
|
||||
|
||||
// Draw current cursor location
|
||||
uiCtx.lineWidth = style.width;
|
||||
uiCtx.strokeStyle = style.style;
|
||||
|
||||
uiCtx.beginPath();
|
||||
uiCtx.moveTo(vpc.x, vpc.y + 10);
|
||||
uiCtx.lineTo(vpc.x, vpc.y - 10);
|
||||
uiCtx.moveTo(vpc.x + 10, vpc.y);
|
||||
uiCtx.lineTo(vpc.x - 10, vpc.y);
|
||||
uiCtx.stroke();
|
||||
return () => {
|
||||
uiCtx.clearRect(vpc.x - 15, vpc.y - 15, vpc.x + 30, vpc.y + 30);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates generic handlers for dealing with draggable selection areas
|
||||
*
|
||||
* @param {object} state State of the tool
|
||||
* @param {boolean} state.snapToGrid Whether the cursor should snap to the grid
|
||||
* @param {() => void} [state.redraw] Function to redraw the cursor
|
||||
* @returns
|
||||
*/
|
||||
_draggable_selection(state) {
|
||||
const selection = {
|
||||
_inside: false,
|
||||
_dirty_bb: true,
|
||||
_cached_bb: null,
|
||||
_selected: null,
|
||||
|
||||
/**
|
||||
* If the cursor is cursor is currently inside the selection
|
||||
*/
|
||||
get inside() {
|
||||
return this._inside;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get intermediate selection object
|
||||
*/
|
||||
get selected() {
|
||||
return this._selected;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the selection exists
|
||||
*/
|
||||
get exists() {
|
||||
return !!this._selected;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the selection bounding box
|
||||
*/
|
||||
get bb() {
|
||||
if (this._dirty_bb && this._selected) {
|
||||
this._cached_bb = BoundingBox.fromStartEnd(
|
||||
this._selected.start,
|
||||
this._selected.now
|
||||
);
|
||||
this._dirty_bb = false;
|
||||
}
|
||||
return this._selected && this._cached_bb;
|
||||
},
|
||||
|
||||
/**
|
||||
* When the cursor enters the selection
|
||||
*/
|
||||
onenter: new Observer(),
|
||||
|
||||
/**
|
||||
* When the cursor leaves the selection
|
||||
*/
|
||||
onleave: new Observer(),
|
||||
|
||||
// Utility methods
|
||||
deselect() {
|
||||
if (this.inside) {
|
||||
this._inside = false;
|
||||
this.onleave.emit({evn: null});
|
||||
}
|
||||
this._selected = null;
|
||||
},
|
||||
|
||||
// Dragging handlers
|
||||
/**
|
||||
* Drag start event handler
|
||||
*
|
||||
* @param {Point} evn Drag start event
|
||||
*/
|
||||
dragstartcb(evn) {
|
||||
const x = state.snapToGrid ? evn.ix + snap(evn.ix, 0, 64) : evn.ix;
|
||||
const y = state.snapToGrid ? evn.iy + snap(evn.iy, 0, 64) : evn.iy;
|
||||
this._selected = {start: {x, y}, now: {x, y}};
|
||||
this._dirty_bb = true;
|
||||
},
|
||||
/**
|
||||
* Drag event handler
|
||||
*
|
||||
* @param {Point} evn Drag event
|
||||
*/
|
||||
dragcb(evn) {
|
||||
const x = state.snapToGrid ? evn.x + snap(evn.x, 0, 64) : evn.x;
|
||||
const y = state.snapToGrid ? evn.y + snap(evn.y, 0, 64) : evn.y;
|
||||
|
||||
if (x !== this._selected.now.x || y !== this._selected.now.y) {
|
||||
this._selected.now = {x, y};
|
||||
this._dirty_bb = true;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Drag end event handler
|
||||
*
|
||||
* @param {Point} evn Drag end event
|
||||
*/
|
||||
dragendcb(evn) {
|
||||
const x = state.snapToGrid ? evn.x + snap(evn.x, 0, 64) : evn.x;
|
||||
const y = state.snapToGrid ? evn.y + snap(evn.y, 0, 64) : evn.y;
|
||||
|
||||
this._selected.now = {x, y};
|
||||
this._dirty_bb = true;
|
||||
|
||||
if (
|
||||
this._selected.start.x === this._selected.now.x ||
|
||||
this._selected.start.y === this._selected.now.y
|
||||
) {
|
||||
this.deselect();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Mouse move event handler
|
||||
*
|
||||
* @param {Point} evn Mouse move event
|
||||
*/
|
||||
smousemovecb(evn) {
|
||||
if (!this._selected || !this.bb.contains(evn.x, evn.y)) {
|
||||
if (this.inside) {
|
||||
this._inside = false;
|
||||
this.onleave.emit({evn});
|
||||
}
|
||||
} else {
|
||||
if (!this.inside) {
|
||||
this._inside = true;
|
||||
this.onenter.emit({evn});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return selection;
|
||||
},
|
||||
};
|
|
@ -131,20 +131,10 @@ const selectTransformTool = () =>
|
|||
);
|
||||
},
|
||||
handles() {
|
||||
const _createHandle = (x, y, originOffset = null, size = 10) => {
|
||||
const _createHandle = (x, y, originOffset = null) => {
|
||||
return {
|
||||
x: x - size / 2,
|
||||
y: y - size / 2,
|
||||
w: size,
|
||||
h: size,
|
||||
contains(x, y) {
|
||||
return (
|
||||
this.x <= x &&
|
||||
x <= this.x + this.w &&
|
||||
this.y <= y &&
|
||||
y <= this.y + this.h
|
||||
);
|
||||
},
|
||||
x,
|
||||
y,
|
||||
scaleTo: (tx, ty, keepAspectRatio = true) => {
|
||||
const origin = {
|
||||
x: this.original.x + this.original.w / 2,
|
||||
|
@ -182,11 +172,13 @@ const selectTransformTool = () =>
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
const size = viewport.zoom * 10;
|
||||
return [
|
||||
_createHandle(this.x, this.y),
|
||||
_createHandle(this.x + this.w, this.y),
|
||||
_createHandle(this.x, this.y + this.h),
|
||||
_createHandle(this.x + this.w, this.y + this.h),
|
||||
_createHandle(this.x, this.y, size),
|
||||
_createHandle(this.x + this.w, this.y, size),
|
||||
_createHandle(this.x, this.y + this.h, size),
|
||||
_createHandle(this.x + this.w, this.y + this.h, size),
|
||||
];
|
||||
},
|
||||
};
|
||||
|
@ -295,10 +287,20 @@ const selectTransformTool = () =>
|
|||
state.selected.handles().forEach((handle) => {
|
||||
const bbvph = {
|
||||
...viewport.canvasToView(handle.x, handle.y),
|
||||
w: viewport.zoom * handle.w,
|
||||
h: viewport.zoom * handle.h,
|
||||
w: 10,
|
||||
h: 10,
|
||||
};
|
||||
if (handle.contains(evn.x, evn.y)) {
|
||||
|
||||
bbvph.x -= 5;
|
||||
bbvph.y -= 5;
|
||||
|
||||
const inhandle =
|
||||
evn.evn.clientX > bbvph.x &&
|
||||
evn.evn.clientX < bbvph.x + bbvph.w &&
|
||||
evn.evn.clientY > bbvph.y &&
|
||||
evn.evn.clientY < bbvph.y + bbvph.h;
|
||||
|
||||
if (inhandle) {
|
||||
cursorInHandle = true;
|
||||
uiCtx.strokeRect(
|
||||
bbvph.x - 1,
|
||||
|
@ -333,10 +335,11 @@ const selectTransformTool = () =>
|
|||
// Handles left mouse clicks
|
||||
state.clickcb = (evn) => {
|
||||
if (
|
||||
state.original.x === state.selected.x &&
|
||||
state.original.y === state.selected.y &&
|
||||
state.original.w === state.selected.w &&
|
||||
state.original.h === state.selected.h
|
||||
!state.original ||
|
||||
(state.original.x === state.selected.x &&
|
||||
state.original.y === state.selected.y &&
|
||||
state.original.w === state.selected.w &&
|
||||
state.original.h === state.selected.h)
|
||||
) {
|
||||
state.reset();
|
||||
return;
|
||||
|
@ -381,9 +384,24 @@ const selectTransformTool = () =>
|
|||
if (state.selected) {
|
||||
const handles = state.selected.handles();
|
||||
|
||||
const activeHandle = handles.find((v) =>
|
||||
v.contains(evn.ix, evn.iy)
|
||||
);
|
||||
const activeHandle = handles.find((v) => {
|
||||
const vpc = viewport.canvasToView(v.x, v.y);
|
||||
const tlc = viewport.viewToCanvas(vpc.x - 5, vpc.y - 5);
|
||||
const brc = viewport.viewToCanvas(vpc.x + 5, vpc.y + 5);
|
||||
const bb = {
|
||||
x: tlc.x,
|
||||
y: tlc.y,
|
||||
w: brc.x - tlc.x,
|
||||
h: brc.y - tlc.y,
|
||||
};
|
||||
|
||||
return (
|
||||
evn.ix > bb.x &&
|
||||
evn.ix < bb.x + bb.w &&
|
||||
evn.iy > bb.y &&
|
||||
evn.iy < bb.y + bb.h
|
||||
);
|
||||
});
|
||||
if (activeHandle) {
|
||||
state.scaling = activeHandle;
|
||||
return;
|
||||
|
|
Loading…
Reference in a new issue