Layers.
Been working on this when I could the last few days. Not quite infinity. Middle mouse button drag, ctrl mouse wheel zoom. Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
4e27770284
commit
44cf9c0e70
26 changed files with 1549 additions and 1346 deletions
|
@ -104,31 +104,27 @@ body {
|
||||||
grid-row-gap: 0px;
|
grid-row-gap: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskCanvasMonitor .overMaskCanvasMonitor .initImgCanvasMonitor {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mask colors for mask inversion */
|
/* Mask colors for mask inversion */
|
||||||
/* Filters are some magic acquired at https://codepen.io/sosuke/pen/Pjoqqp */
|
/* Filters are some magic acquired at https://codepen.io/sosuke/pen/Pjoqqp */
|
||||||
.maskPaintCanvas {
|
.mask-canvas {
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display {
|
.mask-canvas.display {
|
||||||
opacity: 40%;
|
opacity: 40%;
|
||||||
filter: invert(100%);
|
filter: invert(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display.opaque {
|
.mask-canvas.display.opaque {
|
||||||
opacity: 100%;
|
opacity: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display.clear {
|
.mask-canvas.display.clear {
|
||||||
filter: invert(71%) sepia(46%) saturate(6615%) hue-rotate(321deg)
|
filter: invert(71%) sepia(46%) saturate(6615%) hue-rotate(321deg)
|
||||||
brightness(106%) contrast(100%);
|
brightness(106%) contrast(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display.hold {
|
.mask-canvas.display.hold {
|
||||||
filter: invert(41%) sepia(16%) saturate(5181%) hue-rotate(218deg)
|
filter: invert(41%) sepia(16%) saturate(5181%) hue-rotate(218deg)
|
||||||
brightness(103%) contrast(108%);
|
brightness(103%) contrast(108%);
|
||||||
}
|
}
|
||||||
|
@ -215,11 +211,6 @@ div.prompt-wrapper > textarea {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
top: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +219,6 @@ div.prompt-wrapper > textarea:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tool buttons */
|
/* Tool buttons */
|
||||||
|
|
||||||
.button-array {
|
.button-array {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
|
|
|
@ -4,3 +4,43 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#layer-manager .menu-container {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target {
|
||||||
|
position: fixed;
|
||||||
|
background-color: #466;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target .collection {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target .collection > .collection-input-overlay {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target canvas {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,10 @@
|
||||||
background-color: var(--c-primary);
|
background-color: var(--c-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ui-toolbar * {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
#ui-toolbar .handle {
|
#ui-toolbar .handle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
128
index.html
128
index.html
|
@ -191,123 +191,27 @@
|
||||||
<div class="toolbar-section"></div>
|
<div class="toolbar-section"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Layer Allocation View -->
|
|
||||||
<div
|
|
||||||
id="layer-preview"
|
|
||||||
class="floating-window toolbar"
|
|
||||||
style="left: 10px; bottom: 10px">
|
|
||||||
<div class="draggable floating-window-title">Layer Debug View</div>
|
|
||||||
<div class="menu-container" style="min-width: 200px">
|
|
||||||
<canvas class="preview-canvas"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Canvases -->
|
<!-- Canvases -->
|
||||||
<div
|
<div id="layer-render" class="layer-render-target"></div>
|
||||||
id="mainHSplit"
|
|
||||||
class="mainHSplit"
|
|
||||||
onmouseover="document.activeElement.blur()">
|
|
||||||
<div id="uiWrapper" class="uiWrapper">
|
|
||||||
<div
|
|
||||||
id="canvasHolder"
|
|
||||||
class="canvasHolder"
|
|
||||||
oncontextmenu="return false;">
|
|
||||||
<canvas
|
|
||||||
id="backgroundCanvas"
|
|
||||||
class="mainCanvases backgroundCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 0">
|
|
||||||
<!-- gray grid bg canvas -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="canvas"
|
|
||||||
class="mainCanvases canvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 1">
|
|
||||||
<!-- normal canvas on which images are drawn -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="tempCanvas"
|
|
||||||
class="mainCanvases tempCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 2">
|
|
||||||
<!-- temporary canvas on which images being selected/rejected or imported arbitrary images are superimposed -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="targetCanvas"
|
|
||||||
class="mainCanvases targetCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 3">
|
|
||||||
<!-- canvas on which "targeting" squares are drawn -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="maskPaintCanvas"
|
|
||||||
class="mainCanvases maskPaintCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 4">
|
|
||||||
<!-- canvas on which masking brush is "painted" -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="overlayCanvas"
|
|
||||||
class="mainCanvases overlayCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 5">
|
|
||||||
<!-- canvas on which "cursor" reticle or arc is drawn -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<div id="tempDiv" style="position: relative; z-index: 6">
|
|
||||||
<!-- where popup buttons go -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="masks" class="masks">
|
|
||||||
<div>
|
|
||||||
<!-- <canvas id="maskCanvasMonitor" class="maskCanvasMonitor" width="512" height="512">
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas><br /> -->
|
|
||||||
<canvas
|
|
||||||
id="overMaskCanvasMonitor"
|
|
||||||
class="overMaskCanvasMonitor"
|
|
||||||
width="512"
|
|
||||||
height="512">
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<br />
|
|
||||||
<canvas
|
|
||||||
id="initImgCanvasMonitor"
|
|
||||||
class="initImgCanvasMonitor"
|
|
||||||
width="512"
|
|
||||||
height="512">
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Base Libs -->
|
<!-- Base Libs -->
|
||||||
<script src="js/util.js" type="text/javascript"></script>
|
<script src="js/lib/util.js" type="text/javascript"></script>
|
||||||
<script src="js/input.js" type="text/javascript"></script>
|
<script src="js/lib/input.js" type="text/javascript"></script>
|
||||||
<script src="js/layers.js" type="text/javascript"></script>
|
<script src="js/lib/layers.js" type="text/javascript"></script>
|
||||||
|
<script src="js/lib/commands.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<script src="js/commands.js" type="text/javascript"></script>
|
|
||||||
<script src="js/ui/history.js" type="text/javascript"></script>
|
|
||||||
<script src="js/settingsbar.js" type="text/javascript"></script>
|
<script src="js/settingsbar.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<script src="js/lib/toolbar.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="js/initalize/layers.populate.js"
|
||||||
|
type="text/javascript"></script>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<script src="js/index.js" type="text/javascript"></script>
|
<script src="js/index.js" type="text/javascript"></script>
|
||||||
<script src="js/shortcuts.js" type="text/javascript"></script>
|
<script src="js/shortcuts.js" type="text/javascript"></script>
|
||||||
|
<script src="js/ui/floating/history.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<!-- Load Tools -->
|
<!-- Load Tools -->
|
||||||
<script src="js/ui/tool/dream.js" type="text/javascript"></script>
|
<script src="js/ui/tool/dream.js" type="text/javascript"></script>
|
||||||
|
@ -315,6 +219,12 @@
|
||||||
<script src="js/ui/tool/select.js" type="text/javascript"></script>
|
<script src="js/ui/tool/select.js" type="text/javascript"></script>
|
||||||
<script src="js/ui/tool/stamp.js" type="text/javascript"></script>
|
<script src="js/ui/tool/stamp.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<script src="js/ui/toolbar.js" type="text/javascript"></script>
|
<!-- Initialize -->
|
||||||
|
<script
|
||||||
|
src="js/initalize/toolbar.populate.js"
|
||||||
|
type="text/javascript"></script>
|
||||||
|
<script
|
||||||
|
src="js/initalize/debug.populate.js"
|
||||||
|
type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
345
js/index.js
345
js/index.js
|
@ -48,7 +48,6 @@ var stableDiffusionData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// stuff things use
|
// stuff things use
|
||||||
var blockNewImages = false;
|
|
||||||
var returnedImages;
|
var returnedImages;
|
||||||
var imageIndex = 0;
|
var imageIndex = 0;
|
||||||
var tmpImgXYWH = {};
|
var tmpImgXYWH = {};
|
||||||
|
@ -57,15 +56,6 @@ var url = "/sdapi/v1/";
|
||||||
var endpoint = "txt2img";
|
var endpoint = "txt2img";
|
||||||
var frameX = 512;
|
var frameX = 512;
|
||||||
var frameY = 512;
|
var frameY = 512;
|
||||||
var prevMouseX = 0;
|
|
||||||
var prevMouseY = 0;
|
|
||||||
var mouseX = 0;
|
|
||||||
var mouseY = 0;
|
|
||||||
var canvasX = 0;
|
|
||||||
var canvasY = 0;
|
|
||||||
var heldButton = 0;
|
|
||||||
var snapX = 0;
|
|
||||||
var snapY = 0;
|
|
||||||
var drawThis = {};
|
var drawThis = {};
|
||||||
const basePixelCount = 64; //64 px - ALWAYS 64 PX
|
const basePixelCount = 64; //64 px - ALWAYS 64 PX
|
||||||
var scaleFactor = 8; //x64 px
|
var scaleFactor = 8; //x64 px
|
||||||
|
@ -89,44 +79,6 @@ var stopMarching = null;
|
||||||
var inProgress = false;
|
var inProgress = false;
|
||||||
var marchCoords = {};
|
var marchCoords = {};
|
||||||
|
|
||||||
// info div, sometimes hidden
|
|
||||||
let mouseXInfo = document.getElementById("mouseX");
|
|
||||||
let mouseYInfo = document.getElementById("mouseY");
|
|
||||||
let canvasXInfo = document.getElementById("canvasX");
|
|
||||||
let canvasYInfo = document.getElementById("canvasY");
|
|
||||||
let snapXInfo = document.getElementById("snapX");
|
|
||||||
let snapYInfo = document.getElementById("snapY");
|
|
||||||
let heldButtonInfo = document.getElementById("heldButton");
|
|
||||||
|
|
||||||
// canvases and related
|
|
||||||
const ovCanvas = document.getElementById("overlayCanvas"); // where mouse cursor renders
|
|
||||||
const ovCtx = ovCanvas.getContext("2d");
|
|
||||||
const tgtCanvas = document.getElementById("targetCanvas"); // where "box" gets drawn before dream happens
|
|
||||||
const tgtCtx = tgtCanvas.getContext("2d");
|
|
||||||
const maskPaintCanvas = document.getElementById("maskPaintCanvas"); // where masking brush gets painted
|
|
||||||
const maskPaintCtx = maskPaintCanvas.getContext("2d");
|
|
||||||
const tempCanvas = document.getElementById("tempCanvas"); // where select/rejects get superimposed temporarily
|
|
||||||
const tempCtx = tempCanvas.getContext("2d");
|
|
||||||
const imgCanvas = document.getElementById("canvas"); // where dreams go
|
|
||||||
const imgCtx = imgCanvas.getContext("2d");
|
|
||||||
const bgCanvas = document.getElementById("backgroundCanvas"); // gray bg grid
|
|
||||||
const bgCtx = bgCanvas.getContext("2d");
|
|
||||||
|
|
||||||
// Layering
|
|
||||||
const imageCollection = layers.registerCollection("image", {
|
|
||||||
name: "Image Layers",
|
|
||||||
scope: {
|
|
||||||
always: {
|
|
||||||
key: "default",
|
|
||||||
options: {
|
|
||||||
name: "Default Image Layer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true});
|
|
||||||
|
|
||||||
//
|
//
|
||||||
function startup() {
|
function startup() {
|
||||||
testHostConfiguration();
|
testHostConfiguration();
|
||||||
|
@ -163,9 +115,6 @@ function startup() {
|
||||||
changeSeed();
|
changeSeed();
|
||||||
changeOverMaskPx();
|
changeOverMaskPx();
|
||||||
changeHiResFix();
|
changeHiResFix();
|
||||||
document.getElementById("overlayCanvas").onmousemove = mouseMove;
|
|
||||||
document.getElementById("overlayCanvas").onmousedown = mouseDown;
|
|
||||||
document.getElementById("overlayCanvas").onmouseup = mouseUp;
|
|
||||||
document.getElementById("scaleFactor").value = scaleFactor;
|
document.getElementById("scaleFactor").value = scaleFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +146,10 @@ function testHostConfiguration() {
|
||||||
"Host seems to be invalid! Please fix your host here:",
|
"Host seems to be invalid! Please fix your host here:",
|
||||||
current
|
current
|
||||||
);
|
);
|
||||||
|
else
|
||||||
|
host = current.endsWith("/")
|
||||||
|
? current.substring(0, current.length - 1)
|
||||||
|
: current;
|
||||||
} else {
|
} else {
|
||||||
requestHost(
|
requestHost(
|
||||||
"This seems to be the first time you are using openOutpaint! Please set your host here:"
|
"This seems to be the first time you are using openOutpaint! Please set your host here:"
|
||||||
|
@ -205,11 +158,7 @@ function testHostConfiguration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function testHostConnection() {
|
function testHostConnection() {
|
||||||
function CheckInProgressError(message = "") {
|
class CheckInProgressError extends Error {}
|
||||||
this.name = "CheckInProgressError";
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
CheckInProgressError.prototype = Object.create(Error.prototype);
|
|
||||||
|
|
||||||
const connectionIndicator = document.getElementById(
|
const connectionIndicator = document.getElementById(
|
||||||
"connection-status-indicator"
|
"connection-status-indicator"
|
||||||
|
@ -343,131 +292,9 @@ function testHostConnection() {
|
||||||
checkAgain();
|
checkAgain();
|
||||||
}
|
}
|
||||||
|
|
||||||
function dream(
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
prompt,
|
|
||||||
extra = {
|
|
||||||
method: endpoint,
|
|
||||||
stopMarching: () => {},
|
|
||||||
bb: {x, y, w: prompt.width, h: prompt.height},
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
tmpImgXYWH.x = x;
|
|
||||||
tmpImgXYWH.y = y;
|
|
||||||
tmpImgXYWH.w = prompt.width;
|
|
||||||
tmpImgXYWH.h = prompt.height;
|
|
||||||
console.log(
|
|
||||||
"dreaming to " +
|
|
||||||
host +
|
|
||||||
url +
|
|
||||||
(extra.method || endpoint) +
|
|
||||||
":\r\n" +
|
|
||||||
JSON.stringify(prompt)
|
|
||||||
);
|
|
||||||
console.info(`dreaming "${prompt.prompt}"`);
|
|
||||||
console.debug(prompt);
|
|
||||||
|
|
||||||
// Start checking for progress
|
|
||||||
const progressCheck = checkProgress(extra.bb);
|
|
||||||
postData(prompt, extra)
|
|
||||||
.then((data) => {
|
|
||||||
returnedImages = data.images;
|
|
||||||
totalImagesReturned = data.images.length;
|
|
||||||
blockNewImages = true;
|
|
||||||
//console.log(data); // JSON data parsed by `data.json()` call
|
|
||||||
imageAcceptReject(x, y, data, extra);
|
|
||||||
})
|
|
||||||
.finally(() => clearInterval(progressCheck));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function postData(promptData, extra = null) {
|
|
||||||
this.host = document.getElementById("host").value;
|
|
||||||
// Default options are marked with *
|
|
||||||
const response = await fetch(
|
|
||||||
this.host + this.url + extra.method || endpoint,
|
|
||||||
{
|
|
||||||
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
|
||||||
mode: "cors", // no-cors, *cors, same-origin
|
|
||||||
cache: "default", // *default, no-cache, reload, force-cache, only-if-cached
|
|
||||||
credentials: "same-origin", // include, *same-origin, omit
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
redirect: "follow", // manual, *follow, error
|
|
||||||
referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
|
|
||||||
body: JSON.stringify(promptData), // body data type must match "Content-Type" header
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return response.json(); // parses JSON response into native JavaScript objects
|
|
||||||
}
|
|
||||||
|
|
||||||
function imageAcceptReject(x, y, data, extra = null) {
|
|
||||||
inProgress = false;
|
|
||||||
document.getElementById("progressDiv").remove();
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = function () {
|
|
||||||
backupAndClearMask(x, y, img.width, img.height);
|
|
||||||
tempCtx.drawImage(img, x, y); //imgCtx for actual image, tmp for... holding?
|
|
||||||
var div = document.createElement("div");
|
|
||||||
div.id = "veryTempDiv";
|
|
||||||
div.style.position = "absolute";
|
|
||||||
div.style.left = parseInt(x) + "px";
|
|
||||||
div.style.top = parseInt(y + data.parameters.height) + "px";
|
|
||||||
div.style.width = "200px";
|
|
||||||
div.style.height = "70px";
|
|
||||||
div.innerHTML =
|
|
||||||
'<button onclick="prevImg(this)"><</button><button onclick="nextImg(this)">></button><span class="strokeText" id="currentImgIndex"></span><span class="strokeText"> of </span><span class="strokeText" id="totalImgIndex"></span><button onclick="accept(this)">Y</button><button onclick="reject(this)">N</button><button onclick="resource(this)">RES</button><span class="strokeText" id="estRemaining"></span>';
|
|
||||||
|
|
||||||
document.getElementById("tempDiv").appendChild(div);
|
|
||||||
document.getElementById("currentImgIndex").innerText = "1";
|
|
||||||
document.getElementById("totalImgIndex").innerText = totalImagesReturned;
|
|
||||||
};
|
|
||||||
// set the image displayed as the first regardless of batch size/count
|
|
||||||
imageIndex = 0;
|
|
||||||
// load the image data after defining the closure
|
|
||||||
img.src = "data:image/png;base64," + returnedImages[imageIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
function accept(evt) {
|
|
||||||
// write image to imgcanvas
|
|
||||||
stopMarching && stopMarching();
|
|
||||||
stopMarching = null;
|
|
||||||
clearBackupMask();
|
|
||||||
placeImage();
|
|
||||||
removeChoiceButtons();
|
|
||||||
clearTargetMask();
|
|
||||||
blockNewImages = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reject(evt) {
|
|
||||||
// remove image entirely
|
|
||||||
stopMarching && stopMarching();
|
|
||||||
stopMarching = null;
|
|
||||||
restoreBackupMask();
|
|
||||||
clearBackupMask();
|
|
||||||
clearTargetMask();
|
|
||||||
removeChoiceButtons();
|
|
||||||
blockNewImages = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resource(evt) {
|
|
||||||
// send image to resources
|
|
||||||
const img = new Image();
|
|
||||||
// load the image data after defining the closure
|
|
||||||
img.src = "data:image/png;base64," + returnedImages[imageIndex];
|
|
||||||
|
|
||||||
tools.stamp.state.addResource(
|
|
||||||
prompt("Enter new resource name", "Dream Resource"),
|
|
||||||
img
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function newImage(evt) {
|
function newImage(evt) {
|
||||||
clearPaintedMask();
|
clearPaintedMask();
|
||||||
clearBackupMask();
|
clearBackupMask();
|
||||||
clearTargetMask();
|
|
||||||
commands.runCommand("eraseImage", "Clear Canvas", {
|
commands.runCommand("eraseImage", "Clear Canvas", {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -548,10 +375,6 @@ function clearBackupMask() {
|
||||||
backupMaskY = null;
|
backupMaskY = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearTargetMask() {
|
|
||||||
tgtCtx.clearRect(0, 0, tgtCanvas.width, tgtCanvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearImgMask() {
|
function clearImgMask() {
|
||||||
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
|
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
|
||||||
}
|
}
|
||||||
|
@ -581,122 +404,37 @@ function sleep(ms) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function march(bb) {
|
function march(bb) {
|
||||||
|
const expanded = {...bb};
|
||||||
|
expanded.x--;
|
||||||
|
expanded.y--;
|
||||||
|
expanded.w += 2;
|
||||||
|
expanded.h += 2;
|
||||||
|
|
||||||
|
// Get temporary layer to draw marching ants
|
||||||
|
const layer = imageCollection.registerLayer(null, {
|
||||||
|
bb: expanded,
|
||||||
|
});
|
||||||
|
layer.canvas.style.imageRendering = "pixelated";
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
drawMarchingAnts(bb, offset++);
|
drawMarchingAnts(layer.ctx, bb, offset++);
|
||||||
offset %= 16;
|
offset %= 12;
|
||||||
}, 20);
|
}, 20);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
imageCollection.deleteLayer(layer);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawMarchingAnts(bb, offset) {
|
function drawMarchingAnts(ctx, bb, offset) {
|
||||||
clearTargetMask();
|
ctx.clearRect(0, 0, bb.w + 2, bb.h + 2);
|
||||||
tgtCtx.strokeStyle = "#FFFFFFFF"; //"#55000077";
|
ctx.strokeStyle = "#FFFFFFFF"; //"#55000077";
|
||||||
tgtCtx.setLineDash([4, 2]);
|
ctx.strokeWidth = "2px";
|
||||||
tgtCtx.lineDashOffset = -offset;
|
ctx.setLineDash([4, 2]);
|
||||||
tgtCtx.strokeRect(bb.x, bb.y, bb.w, bb.h);
|
ctx.lineDashOffset = -offset;
|
||||||
}
|
ctx.strokeRect(1, 1, bb.w, bb.h);
|
||||||
|
|
||||||
function checkProgress(bb) {
|
|
||||||
document.getElementById("progressDiv") &&
|
|
||||||
document.getElementById("progressDiv").remove();
|
|
||||||
// Skip image to stop using a ton of networking resources
|
|
||||||
endpoint = "progress?skip_current_image=true";
|
|
||||||
var div = document.createElement("div");
|
|
||||||
div.id = "progressDiv";
|
|
||||||
div.style.position = "absolute";
|
|
||||||
div.style.width = "200px";
|
|
||||||
div.style.height = "70px";
|
|
||||||
div.style.left = parseInt(bb.x + bb.w - 100) + "px";
|
|
||||||
div.style.top = parseInt(bb.y + bb.h) + "px";
|
|
||||||
div.innerHTML = '<span class="strokeText" id="estRemaining"></span>';
|
|
||||||
document.getElementById("tempDiv").appendChild(div);
|
|
||||||
return setInterval(() => {
|
|
||||||
fetch(host + url + endpoint)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
var estimate =
|
|
||||||
Math.round(data.progress * 100) +
|
|
||||||
"% :: " +
|
|
||||||
Math.floor(data.eta_relative) +
|
|
||||||
" sec.";
|
|
||||||
|
|
||||||
document.getElementById("estRemaining").innerText = estimate;
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mouseMove(evt) {
|
|
||||||
const rect = ovCanvas.getBoundingClientRect(); // not-quite pixel offset was driving me insane
|
|
||||||
const canvasOffsetX = rect.left;
|
|
||||||
const canvasOffsetY = rect.top;
|
|
||||||
heldButton = evt.buttons;
|
|
||||||
mouseXInfo.innerText = mouseX = evt.clientX;
|
|
||||||
mouseYInfo.innerText = mouseY = evt.clientY;
|
|
||||||
canvasXInfo.innerText = canvasX = parseInt(evt.clientX - rect.left);
|
|
||||||
canvasYInfo.innerText = canvasY = parseInt(evt.clientY - rect.top);
|
|
||||||
snapXInfo.innerText = canvasX + snap(canvasX);
|
|
||||||
snapYInfo.innerText = canvasY + snap(canvasY);
|
|
||||||
heldButtonInfo.innerText = heldButton;
|
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); // clear out the previous mouse cursor
|
|
||||||
if (placingArbitraryImage) {
|
|
||||||
// ugh refactor so this isn't duplicated between arbitrary image and dream reticle modes
|
|
||||||
snapOffsetX = 0;
|
|
||||||
snapOffsetY = 0;
|
|
||||||
if (snapToGrid) {
|
|
||||||
snapOffsetX = snap(canvasX, false);
|
|
||||||
snapOffsetY = snap(canvasY, false);
|
|
||||||
}
|
|
||||||
finalX = snapOffsetX + canvasX;
|
|
||||||
finalY = snapOffsetY + canvasY;
|
|
||||||
ovCtx.drawImage(arbitraryImage, finalX, finalY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mouseDown(evt) {
|
|
||||||
const rect = ovCanvas.getBoundingClientRect();
|
|
||||||
var oddOffset = 0;
|
|
||||||
if (scaleFactor % 2 != 0) {
|
|
||||||
oddOffset = basePixelCount / 2;
|
|
||||||
}
|
|
||||||
if (evt.button == 0) {
|
|
||||||
// left click
|
|
||||||
if (placingArbitraryImage) {
|
|
||||||
var nextBox = {};
|
|
||||||
nextBox.x = evt.offsetX;
|
|
||||||
nextBox.y = evt.offsetY;
|
|
||||||
nextBox.w = arbitraryImageData.width;
|
|
||||||
nextBox.h = arbitraryImageData.height;
|
|
||||||
dropTargets.push(nextBox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mouseUp(evt) {
|
|
||||||
if (evt.button == 0) {
|
|
||||||
// left click
|
|
||||||
if (placingArbitraryImage) {
|
|
||||||
// jeez i REALLY need to refactor tons of this to not be duplicated all over, that's definitely my next chore after figuring out that razza frazza overmask fade
|
|
||||||
var target = dropTargets[dropTargets.length - 1]; //get the last one... why am i storing all of them?
|
|
||||||
snapOffsetX = 0;
|
|
||||||
snapOffsetY = 0;
|
|
||||||
if (snapToGrid) {
|
|
||||||
snapOffsetX = snap(target.x, false);
|
|
||||||
snapOffsetY = snap(target.y, false);
|
|
||||||
}
|
|
||||||
finalX = snapOffsetX + target.x;
|
|
||||||
finalY = snapOffsetY + target.y;
|
|
||||||
|
|
||||||
drawThis.x = finalX;
|
|
||||||
drawThis.y = finalY;
|
|
||||||
drawThis.w = target.w;
|
|
||||||
drawThis.h = target.h;
|
|
||||||
drawIt = drawThis; // i still think this is really stupid and redundant and unnecessary and redundant
|
|
||||||
drop(drawIt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeSampler() {
|
function changeSampler() {
|
||||||
|
@ -815,10 +553,11 @@ function drawBackground() {
|
||||||
// Checkerboard
|
// Checkerboard
|
||||||
let darkTileColor = "#333";
|
let darkTileColor = "#333";
|
||||||
let lightTileColor = "#555";
|
let lightTileColor = "#555";
|
||||||
for (var x = 0; x < bgCanvas.width; x += 64) {
|
for (var x = 0; x < bgLayer.canvas.width; x += 64) {
|
||||||
for (var y = 0; y < bgCanvas.height; y += 64) {
|
for (var y = 0; y < bgLayer.canvas.height; y += 64) {
|
||||||
bgCtx.fillStyle = (x + y) % 128 === 0 ? lightTileColor : darkTileColor;
|
bgLayer.ctx.fillStyle =
|
||||||
bgCtx.fillRect(x, y, 64, 64);
|
(x + y) % 128 === 0 ? lightTileColor : darkTileColor;
|
||||||
|
bgLayer.ctx.fillRect(x, y, 64, 64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1097,6 +836,18 @@ function loadSettings() {
|
||||||
// document.getElementById("overMaskPx").value = Number(_overmask_px);
|
// document.getElementById("overMaskPx").value = Number(_overmask_px);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("mainHSplit").addEventListener("wheel", (evn) => {
|
imageCollection.element.addEventListener(
|
||||||
|
"wheel",
|
||||||
|
(evn) => {
|
||||||
evn.preventDefault();
|
evn.preventDefault();
|
||||||
});
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
||||||
|
imageCollection.element.addEventListener(
|
||||||
|
"contextmenu",
|
||||||
|
(evn) => {
|
||||||
|
evn.preventDefault();
|
||||||
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
|
@ -106,7 +106,7 @@ const infinity = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
infinity._init();
|
//infinity._init();
|
||||||
Array.from(document.getElementsByClassName("display-canvas")).forEach(
|
Array.from(document.getElementsByClassName("display-canvas")).forEach(
|
||||||
(canvas) => infinity.registerViewport(canvas)
|
(canvas) => infinity.registerViewport(canvas)
|
||||||
);
|
);
|
||||||
|
|
29
js/initalize/debug.populate.js
Normal file
29
js/initalize/debug.populate.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// info div, sometimes hidden
|
||||||
|
let mouseXInfo = document.getElementById("mouseX");
|
||||||
|
let mouseYInfo = document.getElementById("mouseY");
|
||||||
|
let canvasXInfo = document.getElementById("canvasX");
|
||||||
|
let canvasYInfo = document.getElementById("canvasY");
|
||||||
|
let snapXInfo = document.getElementById("snapX");
|
||||||
|
let snapYInfo = document.getElementById("snapY");
|
||||||
|
let heldButtonInfo = document.getElementById("heldButton");
|
||||||
|
|
||||||
|
mouse.listen.window.onmousemove.on((evn) => {
|
||||||
|
mouseXInfo.textContent = evn.x;
|
||||||
|
mouseYInfo.textContent = evn.y;
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.world.onmousemove.on((evn) => {
|
||||||
|
canvasXInfo.textContent = evn.x;
|
||||||
|
canvasYInfo.textContent = evn.y;
|
||||||
|
snapXInfo.textContent = snap(evn.x);
|
||||||
|
snapYInfo.textContent = snap(evn.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the debug layer (Just run toggledebug() in the console)
|
||||||
|
*/
|
||||||
|
const toggledebug = () => {
|
||||||
|
const hidden = debugCanvas.style.display === "none";
|
||||||
|
if (hidden) debugLayer.unhide();
|
||||||
|
else debugLayer.hide();
|
||||||
|
};
|
182
js/initalize/layers.populate.js
Normal file
182
js/initalize/layers.populate.js
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
// Layering
|
||||||
|
const imageCollection = layers.registerCollection(
|
||||||
|
"image",
|
||||||
|
{w: 2560, h: 1472},
|
||||||
|
{
|
||||||
|
name: "Image Layers",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const bgLayer = imageCollection.registerLayer("bg", {
|
||||||
|
name: "Background",
|
||||||
|
});
|
||||||
|
const imgLayer = imageCollection.registerLayer("image", {
|
||||||
|
name: "Image",
|
||||||
|
});
|
||||||
|
const maskPaintLayer = imageCollection.registerLayer("mask", {
|
||||||
|
name: "Mask Paint",
|
||||||
|
});
|
||||||
|
const ovLayer = imageCollection.registerLayer("overlay", {
|
||||||
|
name: "Overlay",
|
||||||
|
});
|
||||||
|
const debugLayer = imageCollection.registerLayer("debug", {
|
||||||
|
name: "Debug Layer",
|
||||||
|
});
|
||||||
|
|
||||||
|
const imgCanvas = imgLayer.canvas; // where dreams go
|
||||||
|
const imgCtx = imgLayer.ctx;
|
||||||
|
|
||||||
|
const maskPaintCanvas = maskPaintLayer.canvas; // where mouse cursor renders
|
||||||
|
const maskPaintCtx = maskPaintLayer.ctx;
|
||||||
|
|
||||||
|
maskPaintCanvas.classList.add("mask-canvas");
|
||||||
|
|
||||||
|
const ovCanvas = ovLayer.canvas; // where mouse cursor renders
|
||||||
|
const ovCtx = ovLayer.ctx;
|
||||||
|
|
||||||
|
const debugCanvas = debugLayer.canvas; // where mouse cursor renders
|
||||||
|
const debugCtx = debugLayer.ctx;
|
||||||
|
|
||||||
|
debugLayer.hide(); // Hidden by default
|
||||||
|
|
||||||
|
layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true});
|
||||||
|
|
||||||
|
// Where CSS and javascript magic happens to make the canvas viewport work
|
||||||
|
/**
|
||||||
|
* Ended up using a CSS transforms approach due to more flexibility on transformations
|
||||||
|
* and capability to automagically translate input coordinates to layer space.
|
||||||
|
*/
|
||||||
|
mouse.registerContext(
|
||||||
|
"world",
|
||||||
|
(evn, ctx) => {
|
||||||
|
ctx.coords.prev.x = ctx.coords.pos.x;
|
||||||
|
ctx.coords.prev.y = ctx.coords.pos.y;
|
||||||
|
ctx.coords.pos.x = evn.layerX;
|
||||||
|
ctx.coords.pos.y = evn.layerY;
|
||||||
|
},
|
||||||
|
{target: imageCollection.inputElement}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global viewport object (may be modularized in the future). All
|
||||||
|
* coordinates given are of the center of the viewport
|
||||||
|
*
|
||||||
|
* cx and cy are the viewport's world coordinates, scaled to zoom level.
|
||||||
|
* _x and _y are actual coordinates in the DOM space
|
||||||
|
*
|
||||||
|
* The transform() function does some transforms and writes them to the
|
||||||
|
* provided element.
|
||||||
|
*/
|
||||||
|
const viewport = {
|
||||||
|
get cx() {
|
||||||
|
return this._x * this.zoom;
|
||||||
|
},
|
||||||
|
|
||||||
|
set cx(v) {
|
||||||
|
return (this._x = v / this.zoom);
|
||||||
|
},
|
||||||
|
_x: 0,
|
||||||
|
get cy() {
|
||||||
|
return this._y * this.zoom;
|
||||||
|
},
|
||||||
|
set cy(v) {
|
||||||
|
return (this._y = v / this.zoom);
|
||||||
|
},
|
||||||
|
_y: 0,
|
||||||
|
zoom: 1,
|
||||||
|
rotation: 0,
|
||||||
|
get w() {
|
||||||
|
return (window.innerWidth * 1) / this.zoom;
|
||||||
|
},
|
||||||
|
get h() {
|
||||||
|
return (window.innerHeight * 1) / this.zoom;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Apply transformation
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} el Element to apply CSS transform to
|
||||||
|
*/
|
||||||
|
transform(el) {
|
||||||
|
el.style.transformOrigin = `${this.cx}px ${this.cy}px`;
|
||||||
|
el.style.transform = `scale(${this.zoom}) translate(${-(
|
||||||
|
this._x -
|
||||||
|
this.w / 2
|
||||||
|
)}px, ${-(this._y - this.h / 2)}px)`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let rotation = 0;
|
||||||
|
let lastTime = performance.now();
|
||||||
|
|
||||||
|
const onframe = () => {
|
||||||
|
const nowTime = performance.now();
|
||||||
|
const dt = nowTime - lastTime;
|
||||||
|
rotation += (10 * dt) / 1000.0;
|
||||||
|
|
||||||
|
lastTime = nowTime;
|
||||||
|
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
|
||||||
|
requestAnimationFrame(onframe);
|
||||||
|
};
|
||||||
|
|
||||||
|
onframe();
|
||||||
|
|
||||||
|
viewport.cx = viewport.w / 2;
|
||||||
|
viewport.cy = viewport.h / 2;
|
||||||
|
|
||||||
|
let worldInit = null;
|
||||||
|
|
||||||
|
imageCollection.element.style.transformOrigin = `${viewport.cx}px ${viewport.cy}px`;
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
|
||||||
|
mouse.listen.window.onwheel.on((evn) => {
|
||||||
|
if (evn.evn.ctrlKey) {
|
||||||
|
const pcx = viewport.cx;
|
||||||
|
const pcy = viewport.cy;
|
||||||
|
if (evn.delta < 0) {
|
||||||
|
viewport.zoom *= 1 + Math.abs(evn.delta * 0.0002);
|
||||||
|
} else {
|
||||||
|
viewport.zoom *= 1 - Math.abs(evn.delta * 0.0002);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport.cx = pcx;
|
||||||
|
viewport.cy = pcy;
|
||||||
|
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
|
||||||
|
debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
|
||||||
|
debugCtx.fillStyle = "#F0F";
|
||||||
|
debugCtx.beginPath();
|
||||||
|
debugCtx.arc(viewport.cx, viewport.cy, 5, 0, Math.PI * 2);
|
||||||
|
debugCtx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.window.btn.middle.onpaintstart.on((evn) => {
|
||||||
|
worldInit = {x: viewport.cx, y: viewport.cy};
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.window.btn.middle.onpaint.on((evn) => {
|
||||||
|
if (worldInit) {
|
||||||
|
viewport.cx = worldInit.x + (evn.ix - evn.x) / viewport.zoom;
|
||||||
|
viewport.cy = worldInit.y + (evn.iy - evn.y) / viewport.zoom;
|
||||||
|
|
||||||
|
// Limits
|
||||||
|
viewport.cx = Math.max(Math.min(viewport.cx, imageCollection.size.w), 0);
|
||||||
|
viewport.cy = Math.max(Math.min(viewport.cy, imageCollection.size.h), 0);
|
||||||
|
|
||||||
|
// Draw Viewport location
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
|
||||||
|
debugCtx.fillStyle = "#F0F";
|
||||||
|
debugCtx.beginPath();
|
||||||
|
debugCtx.arc(viewport.cx, viewport.cy, 5, 0, Math.PI * 2);
|
||||||
|
debugCtx.fill();
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.window.btn.middle.onpaintend.on((evn) => {
|
||||||
|
worldInit = null;
|
||||||
|
});
|
27
js/initalize/toolbar.populate.js
Normal file
27
js/initalize/toolbar.populate.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const tools = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dream tool
|
||||||
|
*/
|
||||||
|
tools.dream = dreamTool();
|
||||||
|
tools.img2img = img2imgTool();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mask Editing tools
|
||||||
|
*/
|
||||||
|
toolbar.addSeparator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mask Brush tool
|
||||||
|
*/
|
||||||
|
tools.maskbrush = maskBrushTool();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Editing tools
|
||||||
|
*/
|
||||||
|
toolbar.addSeparator();
|
||||||
|
|
||||||
|
tools.selecttransform = selectTransformTool();
|
||||||
|
tools.stamp = stampTool();
|
||||||
|
|
||||||
|
toolbar.tools[0].enable();
|
172
js/layers.js
172
js/layers.js
|
@ -1,172 +0,0 @@
|
||||||
/**
|
|
||||||
* This is a manager for the many canvas and content layers that compose the application
|
|
||||||
*
|
|
||||||
* It manages canvases and their locations and sizes according to current viewport views
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Errors
|
|
||||||
class LayerNestedScopesError extends Error {
|
|
||||||
// For when a scope is created in another scope
|
|
||||||
}
|
|
||||||
class LayerNoScopeError extends Error {
|
|
||||||
// For when an action that requires a scope is attempted
|
|
||||||
// in a collection with no scope.
|
|
||||||
}
|
|
||||||
|
|
||||||
const layers = {
|
|
||||||
collections: makeWriteOnce({}, "layers.collections"),
|
|
||||||
|
|
||||||
// Registers a new collection
|
|
||||||
registerCollection: (key, options = {}) => {
|
|
||||||
defaultOpt(options, {
|
|
||||||
// If collection is visible on the Layer View Toolbar
|
|
||||||
visible: true,
|
|
||||||
// Display name for the collection
|
|
||||||
name: key,
|
|
||||||
/**
|
|
||||||
* If layer creates a layer scope
|
|
||||||
*
|
|
||||||
* A layer scope is a context where one, and only one layer inside it or its
|
|
||||||
* subscopes can be active at a time. Nested scopes are not supported.
|
|
||||||
* It receives an object of type:
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* // If there must be a selected layer, pass information to create the first
|
|
||||||
* always: {
|
|
||||||
* key,
|
|
||||||
* options
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
scope: null,
|
|
||||||
// Parent collection
|
|
||||||
parent: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Finds the closest parent with a defined scope
|
|
||||||
const findScope = (collection = options.parent) => {
|
|
||||||
if (!collection) return null;
|
|
||||||
|
|
||||||
if (collection.scope) return collection;
|
|
||||||
return findScope(collection._parent);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Path used for logging purposes
|
|
||||||
const _logpath = options.parent
|
|
||||||
? options.parent + "." + key
|
|
||||||
: "layers.collections." + key;
|
|
||||||
|
|
||||||
// If we have a scope already, we can't add a new scope
|
|
||||||
if (options.scope && findScope())
|
|
||||||
throw new LayerNestedScopesError(`Layer scopes must not be nested`);
|
|
||||||
|
|
||||||
const collection = makeWriteOnce(
|
|
||||||
{
|
|
||||||
_parent: options.parent,
|
|
||||||
_logpath,
|
|
||||||
_layers: [],
|
|
||||||
layers: {},
|
|
||||||
|
|
||||||
name: options.name,
|
|
||||||
|
|
||||||
scope: options.scope,
|
|
||||||
// Registers a new layer
|
|
||||||
registerLayer: (key, options = {}) => {
|
|
||||||
defaultOpt(options, {
|
|
||||||
// Display name for the layer
|
|
||||||
name: key,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Path used for logging purposes
|
|
||||||
const _layerlogpath = _logpath + ".layers." + key;
|
|
||||||
const layer = makeWriteOnce(
|
|
||||||
{
|
|
||||||
_logpath: _layerlogpath,
|
|
||||||
id: guid(),
|
|
||||||
name: options.name,
|
|
||||||
|
|
||||||
state: new Proxy(
|
|
||||||
{visible: true},
|
|
||||||
{
|
|
||||||
set(obj, opt, val) {
|
|
||||||
switch (opt) {
|
|
||||||
case "visible":
|
|
||||||
layer.canvas.style.display = val ? "block" : "none";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
obj[opt] = val;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
),
|
|
||||||
|
|
||||||
// This is where black magic will take place in the future
|
|
||||||
// A proxy for the canvas object
|
|
||||||
canvas: new Proxy(document.createElement("canvas"), {}),
|
|
||||||
|
|
||||||
// Activates this layer in the scope
|
|
||||||
activate: () => {
|
|
||||||
const scope = findScope(collection);
|
|
||||||
if (scope) {
|
|
||||||
scope.active = layer;
|
|
||||||
console.debug(
|
|
||||||
`[layers] Layer ${layer._logpath} now active in scope ${scope._logpath}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Deactivates this layer in the scope
|
|
||||||
deactivate: () => {
|
|
||||||
const scope = findScope(collection);
|
|
||||||
if (scope && scope.active === layer) scope.active = null;
|
|
||||||
console.debug();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_layerlogpath
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add to indexers
|
|
||||||
collection._layers.push(layer);
|
|
||||||
collection.layers[key] = layer;
|
|
||||||
|
|
||||||
console.info(
|
|
||||||
`[layers] Layer '${layer.name}' at ${layer._logpath} registered`
|
|
||||||
);
|
|
||||||
return layer;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Deletes a layer
|
|
||||||
deleteLayer: (layer) => {
|
|
||||||
collection._layers.splice(
|
|
||||||
collection._layers.findIndex(
|
|
||||||
(l) => l.id === layer || l.id === layer.id
|
|
||||||
),
|
|
||||||
1
|
|
||||||
);
|
|
||||||
if (typeof layer === "object") {
|
|
||||||
delete collection.layers[layer.id];
|
|
||||||
} else if (typeof layer === "string") {
|
|
||||||
delete collection.layers[layer];
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info(`[layers] Layer '${layer}' deleted`);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_logpath
|
|
||||||
);
|
|
||||||
|
|
||||||
if (parent) parent[key] = collection;
|
|
||||||
else layers.collections[key] = collection;
|
|
||||||
|
|
||||||
console.info(
|
|
||||||
`[layers] Collection '${options.name}' at ${_logpath} registered`
|
|
||||||
);
|
|
||||||
|
|
||||||
// If always, we must create a layer to select
|
|
||||||
if (options.scope && options.scope.always)
|
|
||||||
collection
|
|
||||||
.registerLayer(options.scope.always.key, options.scope.always.options)
|
|
||||||
.activate();
|
|
||||||
|
|
||||||
return collection;
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -44,7 +44,7 @@
|
||||||
* @typedef MouseListenerContext
|
* @typedef MouseListenerContext
|
||||||
* @property {Observer} onmousemove A mouse move handler
|
* @property {Observer} onmousemove A mouse move handler
|
||||||
* @property {Observer} onwheel A mouse wheel handler
|
* @property {Observer} onwheel A mouse wheel handler
|
||||||
* @property {MouseListenerBtnContext} btn Button handlers
|
* @property {Record<string, MouseListenerBtnContext>} btn Button handlers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,7 +65,10 @@
|
||||||
* @property {string} id A unique identifier
|
* @property {string} id A unique identifier
|
||||||
* @property {string} name The key name
|
* @property {string} name The key name
|
||||||
* @property {ContextMoveTransformer} onmove The coordinate transform callback
|
* @property {ContextMoveTransformer} onmove The coordinate transform callback
|
||||||
|
* @property {(evn) => void} onany A function to be run on any event
|
||||||
* @property {?HTMLElement} target The target
|
* @property {?HTMLElement} target The target
|
||||||
|
* @property {MouseCoordContext} coords Coordinates object
|
||||||
|
* @property {MouseListenerContext} listen Listeners object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -64,6 +64,7 @@ const mouse = {
|
||||||
* @param {object} options Extra options
|
* @param {object} options Extra options
|
||||||
* @param {HTMLElement} [options.target=null] Target filtering
|
* @param {HTMLElement} [options.target=null] Target filtering
|
||||||
* @param {Record<number, string>} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping
|
* @param {Record<number, string>} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping
|
||||||
|
* @param {(evn) => void} [options.genericcb=null] Function that will be run for all events (useful for preventDefault)
|
||||||
* @returns {MouseContext}
|
* @returns {MouseContext}
|
||||||
*/
|
*/
|
||||||
registerContext: (name, onmove, options = {}) => {
|
registerContext: (name, onmove, options = {}) => {
|
||||||
|
@ -71,6 +72,7 @@ const mouse = {
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
target: null,
|
target: null,
|
||||||
buttons: {0: "left", 1: "middle", 2: "right"},
|
buttons: {0: "left", 1: "middle", 2: "right"},
|
||||||
|
genericcb: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Context information
|
// Context information
|
||||||
|
@ -79,6 +81,7 @@ const mouse = {
|
||||||
id: guid(),
|
id: guid(),
|
||||||
name,
|
name,
|
||||||
onmove,
|
onmove,
|
||||||
|
onany: options.genericcb,
|
||||||
target: options.target,
|
target: options.target,
|
||||||
buttons: options.buttons,
|
buttons: options.buttons,
|
||||||
};
|
};
|
||||||
|
@ -128,7 +131,9 @@ const mouse = {
|
||||||
const _double_click_timeout = {};
|
const _double_click_timeout = {};
|
||||||
const _drag_start_timeout = {};
|
const _drag_start_timeout = {};
|
||||||
|
|
||||||
window.onmousedown = (evn) => {
|
window.addEventListener(
|
||||||
|
"mousedown",
|
||||||
|
(evn) => {
|
||||||
const time = performance.now();
|
const time = performance.now();
|
||||||
|
|
||||||
if (_double_click_timeout[evn.button]) {
|
if (_double_click_timeout[evn.button]) {
|
||||||
|
@ -178,9 +183,11 @@ window.onmousedown = (evn) => {
|
||||||
|
|
||||||
mouse.buttons[evn.button] = time;
|
mouse.buttons[evn.button] = time;
|
||||||
|
|
||||||
mouse._contexts.forEach(({target, name, buttons}) => {
|
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
||||||
const key = buttons[evn.button];
|
const key = buttons[evn.button];
|
||||||
if ((!target || target === evn.target) && key) {
|
if ((!target || target === evn.target) && key) {
|
||||||
|
onany && onany();
|
||||||
|
|
||||||
mouse.coords[name].dragging[key] = {};
|
mouse.coords[name].dragging[key] = {};
|
||||||
mouse.coords[name].dragging[key].target = evn.target;
|
mouse.coords[name].dragging[key].target = evn.target;
|
||||||
Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos);
|
Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos);
|
||||||
|
@ -196,18 +203,25 @@ window.onmousedown = (evn) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
{
|
||||||
|
passive: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
window.onmouseup = (evn) => {
|
window.addEventListener(
|
||||||
|
"mouseup",
|
||||||
|
(evn) => {
|
||||||
const time = performance.now();
|
const time = performance.now();
|
||||||
|
|
||||||
mouse._contexts.forEach(({target, name, buttons}) => {
|
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
||||||
const key = buttons[evn.button];
|
const key = buttons[evn.button];
|
||||||
if (
|
if (
|
||||||
(!target || target === evn.target) &&
|
(!target || target === evn.target) &&
|
||||||
key &&
|
key &&
|
||||||
mouse.coords[name].dragging[key]
|
mouse.coords[name].dragging[key]
|
||||||
) {
|
) {
|
||||||
|
onany && onany();
|
||||||
const start = {
|
const start = {
|
||||||
x: mouse.coords[name].dragging[key].x,
|
x: mouse.coords[name].dragging[key].x,
|
||||||
y: mouse.coords[name].dragging[key].y,
|
y: mouse.coords[name].dragging[key].y,
|
||||||
|
@ -267,9 +281,13 @@ window.onmouseup = (evn) => {
|
||||||
delete _drag_start_timeout[evn.button];
|
delete _drag_start_timeout[evn.button];
|
||||||
}
|
}
|
||||||
mouse.buttons[evn.button] = null;
|
mouse.buttons[evn.button] = null;
|
||||||
};
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
||||||
window.onmousemove = (evn) => {
|
window.addEventListener(
|
||||||
|
"mousemove",
|
||||||
|
(evn) => {
|
||||||
mouse._contexts.forEach((context) => {
|
mouse._contexts.forEach((context) => {
|
||||||
const target = context.target;
|
const target = context.target;
|
||||||
const name = context.name;
|
const name = context.name;
|
||||||
|
@ -353,7 +371,9 @@ window.onmousemove = (evn) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"wheel",
|
"wheel",
|
||||||
|
@ -382,17 +402,6 @@ mouse.registerContext("window", (evn, ctx) => {
|
||||||
ctx.coords.pos.x = evn.clientX;
|
ctx.coords.pos.x = evn.clientX;
|
||||||
ctx.coords.pos.y = evn.clientY;
|
ctx.coords.pos.y = evn.clientY;
|
||||||
});
|
});
|
||||||
|
|
||||||
mouse.registerContext(
|
|
||||||
"canvas",
|
|
||||||
(evn, ctx) => {
|
|
||||||
ctx.coords.prev.x = ctx.coords.pos.x;
|
|
||||||
ctx.coords.prev.y = ctx.coords.pos.y;
|
|
||||||
ctx.coords.pos.x = evn.layerX;
|
|
||||||
ctx.coords.pos.y = evn.layerY;
|
|
||||||
},
|
|
||||||
document.getElementById("overlayCanvas")
|
|
||||||
);
|
|
||||||
/**
|
/**
|
||||||
* Keyboard input processing
|
* Keyboard input processing
|
||||||
*/
|
*/
|
264
js/lib/layers.js
Normal file
264
js/lib/layers.js
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
/**
|
||||||
|
* This is a manager for the many canvas and content layers that compose the application
|
||||||
|
*
|
||||||
|
* It manages canvases and their locations and sizes according to current viewport views
|
||||||
|
*/
|
||||||
|
const layers = {
|
||||||
|
_collections: [],
|
||||||
|
collections: makeWriteOnce({}, "layers.collections"),
|
||||||
|
|
||||||
|
listen: {
|
||||||
|
oncollectioncreate: new Observer(),
|
||||||
|
oncollectiondelete: new Observer(),
|
||||||
|
|
||||||
|
onlayercreate: new Observer(),
|
||||||
|
onlayerdelete: new Observer(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Registers a new collection
|
||||||
|
// Layer collections are a group of layers (canvases) that are rendered in tandem. (same width, height, position, transform, etc)
|
||||||
|
registerCollection: (key, size, options = {}) => {
|
||||||
|
defaultOpt(options, {
|
||||||
|
// Display name for the collection
|
||||||
|
name: key,
|
||||||
|
|
||||||
|
// Initial layer
|
||||||
|
initLayer: {
|
||||||
|
key: "default",
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Target
|
||||||
|
targetElement: document.getElementById("layer-render"),
|
||||||
|
|
||||||
|
// Resolution of the image
|
||||||
|
resolution: size,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Path used for logging purposes
|
||||||
|
const _logpath = "layers.collections." + key;
|
||||||
|
|
||||||
|
// Collection ID
|
||||||
|
const id = guid();
|
||||||
|
|
||||||
|
// Collection element
|
||||||
|
const element = document.createElement("div");
|
||||||
|
element.id = `collection-${id}`;
|
||||||
|
element.style.width = `${size.w}px`;
|
||||||
|
element.style.height = `${size.h}px`;
|
||||||
|
element.classList.add("collection");
|
||||||
|
|
||||||
|
// Input element (overlay element for input handling)
|
||||||
|
const inputel = document.createElement("div");
|
||||||
|
inputel.id = `collection-input-${id}`;
|
||||||
|
inputel.style.width = `${size.w}px`;
|
||||||
|
inputel.style.height = `${size.h}px`;
|
||||||
|
inputel.addEventListener("mouseover", (evn) => {
|
||||||
|
document.activeElement.blur();
|
||||||
|
});
|
||||||
|
inputel.classList.add("collection-input-overlay");
|
||||||
|
element.appendChild(inputel);
|
||||||
|
|
||||||
|
options.targetElement.appendChild(element);
|
||||||
|
|
||||||
|
const collection = makeWriteOnce(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
|
||||||
|
_logpath,
|
||||||
|
|
||||||
|
_layers: [],
|
||||||
|
layers: {},
|
||||||
|
|
||||||
|
name: options.name,
|
||||||
|
element,
|
||||||
|
inputElement: inputel,
|
||||||
|
|
||||||
|
size,
|
||||||
|
resolution: options.resolution,
|
||||||
|
|
||||||
|
active: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new layer
|
||||||
|
*
|
||||||
|
* @param {string | null} key Name and key to use to access layer. If null, it is a temporary layer.
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.name
|
||||||
|
* @param {?BoundingBox} options.bb
|
||||||
|
* @param {{w: number, h: number}} options.resolution
|
||||||
|
* @param {object} options.after
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
registerLayer: (key = null, options = {}) => {
|
||||||
|
// Make ID
|
||||||
|
const id = guid();
|
||||||
|
|
||||||
|
defaultOpt(options, {
|
||||||
|
// Display name for the layer
|
||||||
|
name: key || `Temporary ${id}`,
|
||||||
|
|
||||||
|
// Bounding box for layer
|
||||||
|
bb: {x: 0, y: 0, w: collection.size.w, h: collection.size.h},
|
||||||
|
|
||||||
|
// Bounding box for layer
|
||||||
|
resolution: null,
|
||||||
|
|
||||||
|
// If set, will insert the layer after the given one
|
||||||
|
after: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate resolution
|
||||||
|
if (!options.resolution)
|
||||||
|
options.resolution = {
|
||||||
|
w: (collection.resolution.w / collection.size.w) * options.bb.w,
|
||||||
|
h: (collection.resolution.h / collection.size.h) * options.bb.h,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This layer's canvas
|
||||||
|
// This is where black magic will take place in the future
|
||||||
|
/**
|
||||||
|
* @todo Use the canvas black arts to auto-scale canvas
|
||||||
|
*/
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.id = `layer-${id}`;
|
||||||
|
|
||||||
|
canvas.style.left = `${options.bb.x}px`;
|
||||||
|
canvas.style.top = `${options.bb.y}px`;
|
||||||
|
canvas.style.width = `${options.bb.w}px`;
|
||||||
|
canvas.style.height = `${options.bb.h}px`;
|
||||||
|
canvas.width = options.resolution.w;
|
||||||
|
canvas.height = options.resolution.h;
|
||||||
|
|
||||||
|
if (!options.after) collection.element.appendChild(canvas);
|
||||||
|
else {
|
||||||
|
options.after.canvas.after(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// Path used for logging purposes
|
||||||
|
const _layerlogpath = key
|
||||||
|
? _logpath + ".layers." + key
|
||||||
|
: _logpath + ".layers[" + id + "]";
|
||||||
|
const layer = makeWriteOnce(
|
||||||
|
{
|
||||||
|
_logpath: _layerlogpath,
|
||||||
|
_collection: collection,
|
||||||
|
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
name: options.name,
|
||||||
|
|
||||||
|
state: new Proxy(
|
||||||
|
{visible: true},
|
||||||
|
{
|
||||||
|
set(obj, opt, val) {
|
||||||
|
switch (opt) {
|
||||||
|
case "visible":
|
||||||
|
layer.canvas.style.display = val ? "block" : "none";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
obj[opt] = val;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
/** Our canvas */
|
||||||
|
canvas,
|
||||||
|
ctx,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves this layer to another location
|
||||||
|
*
|
||||||
|
* @param {number} x X coordinate of the top left of the canvas
|
||||||
|
* @param {number} y X coordinate of the top left of the canvas
|
||||||
|
*/
|
||||||
|
moveTo(x, y) {
|
||||||
|
canvas.style.left = `${x}px`;
|
||||||
|
canvas.style.top = `${y}px`;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hides this layer (don't draw)
|
||||||
|
hide() {
|
||||||
|
this.canvas.style.display = "none";
|
||||||
|
},
|
||||||
|
// Hides this layer (don't draw)
|
||||||
|
unhide() {
|
||||||
|
this.canvas.style.display = "block";
|
||||||
|
},
|
||||||
|
|
||||||
|
// Activates this layer
|
||||||
|
activate() {
|
||||||
|
collection.active = this;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_layerlogpath,
|
||||||
|
["active"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to indexers
|
||||||
|
if (!options.after) collection._layers.push(layer);
|
||||||
|
else {
|
||||||
|
const index = collection._layers.findIndex(
|
||||||
|
(l) => l === options.after
|
||||||
|
);
|
||||||
|
collection._layers.splice(index, 0, layer);
|
||||||
|
}
|
||||||
|
if (key) collection.layers[key] = layer;
|
||||||
|
|
||||||
|
if (key === null)
|
||||||
|
console.debug(
|
||||||
|
`[layers] Anonymous layer '${layer.name}' registered`
|
||||||
|
);
|
||||||
|
else
|
||||||
|
console.info(
|
||||||
|
`[layers] Layer '${layer.name}' at ${layer._logpath} registered`
|
||||||
|
);
|
||||||
|
|
||||||
|
layers.listen.onlayercreate.emit({
|
||||||
|
layer,
|
||||||
|
});
|
||||||
|
return layer;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Deletes a layer
|
||||||
|
deleteLayer: (layer) => {
|
||||||
|
const lobj = collection._layers.splice(
|
||||||
|
collection._layers.findIndex(
|
||||||
|
(l) => l.id === layer || l.id === layer.id
|
||||||
|
),
|
||||||
|
1
|
||||||
|
)[0];
|
||||||
|
if (!lobj) return;
|
||||||
|
|
||||||
|
layers.listen.onlayerdelete.emit({
|
||||||
|
layer: lobj,
|
||||||
|
});
|
||||||
|
if (lobj.key) delete collection.layers[lobj.key];
|
||||||
|
|
||||||
|
collection.element.removeChild(lobj.canvas);
|
||||||
|
|
||||||
|
if (lobj.key) console.info(`[layers] Layer '${lobj.key}' deleted`);
|
||||||
|
else console.debug(`[layers] Anonymous layer '${lobj.id}' deleted`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_logpath,
|
||||||
|
["active"]
|
||||||
|
);
|
||||||
|
|
||||||
|
layers._collections.push(collection);
|
||||||
|
layers.collections[key] = collection;
|
||||||
|
|
||||||
|
console.info(
|
||||||
|
`[layers] Collection '${options.name}' at ${_logpath} registered`
|
||||||
|
);
|
||||||
|
|
||||||
|
// We must create a layer to select
|
||||||
|
collection
|
||||||
|
.registerLayer(options.initLayer.key, options.initLayer.options)
|
||||||
|
.activate();
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
},
|
||||||
|
};
|
|
@ -177,7 +177,6 @@ const _toolbar_input = {
|
||||||
* Dream and img2img tools
|
* Dream and img2img tools
|
||||||
*/
|
*/
|
||||||
const _reticle_draw = (evn, snapToGrid = true) => {
|
const _reticle_draw = (evn, snapToGrid = true) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -190,33 +189,9 @@ const _reticle_draw = (evn, snapToGrid = true) => {
|
||||||
ovCtx.lineWidth = 1;
|
ovCtx.lineWidth = 1;
|
||||||
ovCtx.strokeStyle = "#FFF";
|
ovCtx.strokeStyle = "#FFF";
|
||||||
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame
|
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame
|
||||||
}
|
// TEMP
|
||||||
|
ovCtx.fillStyle = "#0FF";
|
||||||
|
ovCtx.beginPath();
|
||||||
|
ovCtx.arc(evn.x, evn.y, 5, 0, Math.PI * 2);
|
||||||
|
ovCtx.fill();
|
||||||
};
|
};
|
||||||
|
|
||||||
const tools = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dream tool
|
|
||||||
*/
|
|
||||||
tools.dream = dreamTool();
|
|
||||||
tools.img2img = img2imgTool();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mask Editing tools
|
|
||||||
*/
|
|
||||||
toolbar.addSeparator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mask Brush tool
|
|
||||||
*/
|
|
||||||
tools.maskbrush = maskBrushTool();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image Editing tools
|
|
||||||
*/
|
|
||||||
toolbar.addSeparator();
|
|
||||||
|
|
||||||
tools.selecttransform = selectTransformTool();
|
|
||||||
tools.stamp = stampTool();
|
|
||||||
|
|
||||||
toolbar.tools[0].enable();
|
|
|
@ -150,7 +150,7 @@ function createSlider(name, wrapper, options = {}) {
|
||||||
});
|
});
|
||||||
|
|
||||||
mouse.listen.window.btn.left.ondrag.on((evn) => {
|
mouse.listen.window.btn.left.ondrag.on((evn) => {
|
||||||
if (evn.target === overEl) {
|
if (evn.initialTarget === overEl) {
|
||||||
setValue(
|
setValue(
|
||||||
Math.max(
|
Math.max(
|
||||||
options.min,
|
options.min,
|
||||||
|
|
3
js/ui/explore.js
Normal file
3
js/ui/explore.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/**
|
||||||
|
* This is a simple implementation of layer interaction
|
||||||
|
*/
|
23
js/ui/tool/dream.d.js
Normal file
23
js/ui/tool/dream.d.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Stable Diffusion Request
|
||||||
|
*
|
||||||
|
* @typedef StableDiffusionRequest
|
||||||
|
* @property {string} prompt Stable Diffusion prompt
|
||||||
|
* @property {string} negative_prompt Stable Diffusion negative prompt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable Diffusion Response
|
||||||
|
*
|
||||||
|
* @typedef StableDiffusionResponse
|
||||||
|
* @property {string[]} images Response images
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable Diffusion Progress Response
|
||||||
|
*
|
||||||
|
* @typedef StableDiffusionProgressResponse
|
||||||
|
* @property {number} progress Progress (from 0 to 1)
|
||||||
|
* @property {number} eta_relative Estimated finish time
|
||||||
|
* @property {?string} current_image Progress image
|
||||||
|
*/
|
|
@ -1,5 +1,256 @@
|
||||||
const dream_generate_callback = (evn, state) => {
|
let blockNewImages = false;
|
||||||
if (evn.target.id === "overlayCanvas" && !blockNewImages) {
|
|
||||||
|
/**
|
||||||
|
* Starts progress monitoring bar
|
||||||
|
*
|
||||||
|
* @param {BoundingBox} bb Bouding Box to draw progress to
|
||||||
|
* @returns {() => void}
|
||||||
|
*/
|
||||||
|
const _monitorProgress = (bb) => {
|
||||||
|
const minDelay = 1000;
|
||||||
|
|
||||||
|
const apiURL = `${host}${url}progress?skip_current_image=true`;
|
||||||
|
|
||||||
|
const expanded = {...bb};
|
||||||
|
expanded.x--;
|
||||||
|
expanded.y--;
|
||||||
|
expanded.w += 2;
|
||||||
|
expanded.h += 2;
|
||||||
|
|
||||||
|
// Get temporary layer to draw progress bar
|
||||||
|
const layer = imageCollection.registerLayer(null, {
|
||||||
|
bb: expanded,
|
||||||
|
});
|
||||||
|
layer.canvas.style.opacity = "70%";
|
||||||
|
|
||||||
|
let running = true;
|
||||||
|
|
||||||
|
const _checkProgress = async () => {
|
||||||
|
const init = performance.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiURL);
|
||||||
|
/** @type {StableDiffusionProgressResponse} */
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Draw Progress Bar
|
||||||
|
layer.ctx.fillStyle = "#5F5";
|
||||||
|
layer.ctx.fillRect(1, 1, bb.w * data.progress, 10);
|
||||||
|
|
||||||
|
// Draw Progress Text
|
||||||
|
layer.ctx.clearRect(0, 11, expanded.w, 40);
|
||||||
|
layer.ctx.fillStyle = "#FFF";
|
||||||
|
|
||||||
|
layer.ctx.fillRect(0, 15, 60, 25);
|
||||||
|
layer.ctx.fillRect(bb.w - 58, 15, 60, 25);
|
||||||
|
|
||||||
|
layer.ctx.font = "20px Open Sans";
|
||||||
|
layer.ctx.fillStyle = "#000";
|
||||||
|
layer.ctx.textAlign = "right";
|
||||||
|
layer.ctx.fillText(`${Math.round(data.progress * 100)}%`, 55, 35);
|
||||||
|
|
||||||
|
// Draw ETA Text
|
||||||
|
layer.ctx.fillText(`${Math.round(data.eta_relative)}s`, bb.w - 5, 35);
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeSpent = performance.now() - init;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (running) _checkProgress();
|
||||||
|
}, Math.max(0, minDelay - timeSpent));
|
||||||
|
};
|
||||||
|
|
||||||
|
_checkProgress();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
imageCollection.deleteLayer(layer);
|
||||||
|
running = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a dream
|
||||||
|
*
|
||||||
|
* @param {"txt2img" | "img2img"} endpoint Endpoint to send the request to
|
||||||
|
* @param {StableDiffusionRequest} request Stable diffusion request
|
||||||
|
* @returns {Promise<string[]>}
|
||||||
|
*/
|
||||||
|
const _dream = async (endpoint, request) => {
|
||||||
|
const apiURL = `${host}${url}${endpoint}`;
|
||||||
|
|
||||||
|
/** @type {StableDiffusionResponse} */
|
||||||
|
let data = null;
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiURL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
|
||||||
|
data = await response.json();
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.images;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate and pick an image for placement
|
||||||
|
*
|
||||||
|
* @param {"txt2img" | "img2img"} endpoint Endpoint to send the request to
|
||||||
|
* @param {StableDiffusionRequest} request Stable diffusion request
|
||||||
|
* @param {BoundingBox} bb Generated image placement location
|
||||||
|
* @returns {Promise<HTMLImageElement | null>}
|
||||||
|
*/
|
||||||
|
const _generate = async (endpoint, request, bb) => {
|
||||||
|
const requestCopy = {...request};
|
||||||
|
|
||||||
|
// Images to select through
|
||||||
|
let at = 0;
|
||||||
|
/** @type {Image[]} */
|
||||||
|
const images = [];
|
||||||
|
/** @type {HTMLDivElement} */
|
||||||
|
let imageSelectMenu = null;
|
||||||
|
|
||||||
|
// Layer for the images
|
||||||
|
const layer = imageCollection.registerLayer(null, {
|
||||||
|
bb,
|
||||||
|
after: imgLayer,
|
||||||
|
});
|
||||||
|
|
||||||
|
const redraw = () => {
|
||||||
|
const image = new Image();
|
||||||
|
image.src = "data:image/png;base64," + images[at];
|
||||||
|
image.addEventListener("load", () => {
|
||||||
|
layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
||||||
|
if (images[at]) layer.ctx.drawImage(image, 0, 0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopMarchingAnts = march(bb);
|
||||||
|
|
||||||
|
// First Dream Run
|
||||||
|
let stopProgress = _monitorProgress(bb);
|
||||||
|
images.push(...(await _dream(endpoint, requestCopy)));
|
||||||
|
stopProgress();
|
||||||
|
|
||||||
|
// Cleans up
|
||||||
|
const clean = () => {
|
||||||
|
stopMarchingAnts();
|
||||||
|
imageCollection.inputElement.removeChild(imageSelectMenu);
|
||||||
|
imageCollection.deleteLayer(layer);
|
||||||
|
blockNewImages = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeElement = (type, x, y) => {
|
||||||
|
const el = document.createElement(type);
|
||||||
|
el.style.position = "absolute";
|
||||||
|
el.style.left = `${x}px`;
|
||||||
|
el.style.top = `${y}px`;
|
||||||
|
|
||||||
|
// We can use the input element to add interactible html elements in the world
|
||||||
|
imageCollection.inputElement.appendChild(el);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
|
||||||
|
imageSelectMenu = makeElement("div", bb.x, bb.y + bb.h);
|
||||||
|
|
||||||
|
const imageindextxt = document.createElement("button");
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
imageindextxt.addEventListener("click", () => {
|
||||||
|
at = 0;
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
|
||||||
|
const backbtn = document.createElement("button");
|
||||||
|
backbtn.textContent = "<";
|
||||||
|
backbtn.title = "Previous Image";
|
||||||
|
backbtn.addEventListener("click", () => {
|
||||||
|
at--;
|
||||||
|
if (at < 0) at = images.length - 1;
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(backbtn);
|
||||||
|
imageSelectMenu.appendChild(imageindextxt);
|
||||||
|
|
||||||
|
const nextbtn = document.createElement("button");
|
||||||
|
nextbtn.textContent = ">";
|
||||||
|
nextbtn.title = "Next Image";
|
||||||
|
nextbtn.addEventListener("click", () => {
|
||||||
|
at++;
|
||||||
|
if (at >= images.length) at = 0;
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(nextbtn);
|
||||||
|
|
||||||
|
const morebtn = document.createElement("button");
|
||||||
|
morebtn.textContent = "+";
|
||||||
|
morebtn.title = "Generate More";
|
||||||
|
morebtn.addEventListener("click", async () => {
|
||||||
|
let stopProgress = _monitorProgress(bb);
|
||||||
|
images.push(...(await _dream(endpoint, requestCopy)));
|
||||||
|
stopProgress();
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(morebtn);
|
||||||
|
|
||||||
|
const acceptbtn = document.createElement("button");
|
||||||
|
acceptbtn.textContent = "Y";
|
||||||
|
acceptbtn.title = "Apply Current";
|
||||||
|
acceptbtn.addEventListener("click", async () => {
|
||||||
|
commands.runCommand("drawImage", "Image Dream", {
|
||||||
|
x: bb.x,
|
||||||
|
y: bb.y,
|
||||||
|
image: layer.canvas,
|
||||||
|
});
|
||||||
|
clean();
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(acceptbtn);
|
||||||
|
|
||||||
|
const discardbtn = document.createElement("button");
|
||||||
|
discardbtn.textContent = "N";
|
||||||
|
discardbtn.title = "Cancel";
|
||||||
|
discardbtn.addEventListener("click", async () => {
|
||||||
|
clean();
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(discardbtn);
|
||||||
|
|
||||||
|
const resourcebtn = document.createElement("button");
|
||||||
|
resourcebtn.textContent = "R";
|
||||||
|
resourcebtn.title = "Save to Resources";
|
||||||
|
resourcebtn.addEventListener("click", async () => {
|
||||||
|
const img = new Image();
|
||||||
|
// load the image data after defining the closure
|
||||||
|
img.src = "data:image/png;base64," + images[at];
|
||||||
|
img.addEventListener("load", () => {
|
||||||
|
const response = prompt("Enter new resource name", "Dream Resource");
|
||||||
|
if (response) tools.stamp.state.addResource(response, img);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(resourcebtn);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for generating a image (dream tool)
|
||||||
|
*
|
||||||
|
* @param {*} evn
|
||||||
|
* @param {*} state
|
||||||
|
*/
|
||||||
|
const dream_generate_callback = async (evn, state) => {
|
||||||
|
if (!blockNewImages) {
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -19,9 +270,6 @@ const dream_generate_callback = (evn, state) => {
|
||||||
// Don't allow another image until is finished
|
// Don't allow another image until is finished
|
||||||
blockNewImages = true;
|
blockNewImages = true;
|
||||||
|
|
||||||
// Setup marching ants
|
|
||||||
stopMarching = march(bb);
|
|
||||||
|
|
||||||
// Setup some basic information for SD
|
// Setup some basic information for SD
|
||||||
request.width = bb.w;
|
request.width = bb.w;
|
||||||
request.height = bb.h;
|
request.height = bb.h;
|
||||||
|
@ -32,7 +280,7 @@ const dream_generate_callback = (evn, state) => {
|
||||||
// Use txt2img if canvas is blank
|
// Use txt2img if canvas is blank
|
||||||
if (isCanvasBlank(bb.x, bb.y, bb.w, bb.h, imgCanvas)) {
|
if (isCanvasBlank(bb.x, bb.y, bb.w, bb.h, imgCanvas)) {
|
||||||
// Dream
|
// Dream
|
||||||
dream(bb.x, bb.y, request, {method: "txt2img", stopMarching, bb});
|
_generate("txt2img", request, bb);
|
||||||
} else {
|
} else {
|
||||||
// Use img2img if not
|
// Use img2img if not
|
||||||
|
|
||||||
|
@ -101,7 +349,7 @@ const dream_generate_callback = (evn, state) => {
|
||||||
auxCtx.fillRect(0, 0, bb.w, bb.h);
|
auxCtx.fillRect(0, 0, bb.w, bb.h);
|
||||||
request.mask = auxCanvas.toDataURL();
|
request.mask = auxCanvas.toDataURL();
|
||||||
// Dream
|
// Dream
|
||||||
dream(bb.x, bb.y, request, {method: "img2img", stopMarching, bb});
|
_generate("img2img", request, bb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -148,7 +396,7 @@ function applyOvermask(canvas, ctx, px) {
|
||||||
* Image to Image
|
* Image to Image
|
||||||
*/
|
*/
|
||||||
const dream_img2img_callback = (evn, state) => {
|
const dream_img2img_callback = (evn, state) => {
|
||||||
if (evn.target.id === "overlayCanvas" && !blockNewImages) {
|
if (!blockNewImages) {
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -233,7 +481,7 @@ const dream_img2img_callback = (evn, state) => {
|
||||||
request.inpaint_full_res = state.fullResolution;
|
request.inpaint_full_res = state.fullResolution;
|
||||||
|
|
||||||
// Dream
|
// Dream
|
||||||
dream(bb.x, bb.y, request, {method: "img2img", stopMarching, bb});
|
_generate("img2img", request, bb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -248,23 +496,22 @@ const dreamTool = () =>
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.mousemovecb({
|
state.mousemovecb({
|
||||||
...mouse.coords.canvas.pos,
|
...mouse.coords.world.pos,
|
||||||
target: {id: "overlayCanvas"},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.mousemovecb);
|
mouse.listen.world.onmousemove.on(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.on(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.erasecb);
|
mouse.listen.world.btn.right.onclick.on(state.erasecb);
|
||||||
|
|
||||||
// Display Mask
|
// Display Mask
|
||||||
setMask(state.invertMask ? "hold" : "clear");
|
setMask(state.invertMask ? "hold" : "clear");
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.mousemovecb);
|
mouse.listen.world.onmousemove.clear(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.clear(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.erasecb);
|
mouse.listen.world.btn.right.onclick.clear(state.erasecb);
|
||||||
|
|
||||||
// Hide Mask
|
// Hide Mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
@ -274,7 +521,10 @@ const dreamTool = () =>
|
||||||
state.snapToGrid = true;
|
state.snapToGrid = true;
|
||||||
state.invertMask = false;
|
state.invertMask = false;
|
||||||
state.overMaskPx = 0;
|
state.overMaskPx = 0;
|
||||||
state.mousemovecb = (evn) => _reticle_draw(evn, state.snapToGrid);
|
state.mousemovecb = (evn) => {
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
_reticle_draw(evn, state.snapToGrid);
|
||||||
|
};
|
||||||
state.dreamcb = (evn) => {
|
state.dreamcb = (evn) => {
|
||||||
dream_generate_callback(evn, state);
|
dream_generate_callback(evn, state);
|
||||||
};
|
};
|
||||||
|
@ -330,23 +580,22 @@ const img2imgTool = () =>
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.mousemovecb({
|
state.mousemovecb({
|
||||||
...mouse.coords.canvas.pos,
|
...mouse.coords.world.pos,
|
||||||
target: {id: "overlayCanvas"},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.mousemovecb);
|
mouse.listen.world.onmousemove.on(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.on(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.erasecb);
|
mouse.listen.world.btn.right.onclick.on(state.erasecb);
|
||||||
|
|
||||||
// Display Mask
|
// Display Mask
|
||||||
setMask(state.invertMask ? "hold" : "clear");
|
setMask(state.invertMask ? "hold" : "clear");
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.mousemovecb);
|
mouse.listen.world.onmousemove.clear(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.clear(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.erasecb);
|
mouse.listen.world.btn.right.onclick.clear(state.erasecb);
|
||||||
|
|
||||||
// Hide mask
|
// Hide mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
@ -362,8 +611,8 @@ const img2imgTool = () =>
|
||||||
state.keepBorderSize = 64;
|
state.keepBorderSize = 64;
|
||||||
|
|
||||||
state.mousemovecb = (evn) => {
|
state.mousemovecb = (evn) => {
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
_reticle_draw(evn, state.snapToGrid);
|
_reticle_draw(evn, state.snapToGrid);
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -394,13 +643,13 @@ const img2imgTool = () =>
|
||||||
bb.w,
|
bb.w,
|
||||||
state.keepBorderSize
|
state.keepBorderSize
|
||||||
);
|
);
|
||||||
|
console.debug("hey");
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmp = ovCtx.globalAlpha;
|
const tmp = ovCtx.globalAlpha;
|
||||||
ovCtx.globalAlpha = 0.4;
|
ovCtx.globalAlpha = 0.4;
|
||||||
ovCtx.drawImage(auxCanvas, bb.x, bb.y);
|
ovCtx.drawImage(auxCanvas, bb.x, bb.y);
|
||||||
ovCtx.globalAlpha = tmp;
|
ovCtx.globalAlpha = tmp;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
state.dreamcb = (evn) => {
|
state.dreamcb = (evn) => {
|
||||||
dream_img2img_callback(evn, state);
|
dream_img2img_callback(evn, state);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const setMask = (state) => {
|
const setMask = (state) => {
|
||||||
const canvas = document.querySelector("#maskPaintCanvas");
|
const canvas = imageCollection.layers.mask.canvas;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "clear":
|
case "clear":
|
||||||
canvas.classList.remove("hold");
|
canvas.classList.remove("hold");
|
||||||
|
@ -23,10 +23,6 @@ const setMask = (state) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const _mask_brush_draw_callback = (evn, state) => {
|
const _mask_brush_draw_callback = (evn, state) => {
|
||||||
if (
|
|
||||||
(evn.initialTarget && evn.initialTarget.id === "overlayCanvas") ||
|
|
||||||
(!evn.initialTarget && evn.target.id === "overlayCanvas")
|
|
||||||
) {
|
|
||||||
maskPaintCtx.globalCompositeOperation = "source-over";
|
maskPaintCtx.globalCompositeOperation = "source-over";
|
||||||
maskPaintCtx.strokeStyle = "black";
|
maskPaintCtx.strokeStyle = "black";
|
||||||
|
|
||||||
|
@ -39,14 +35,9 @@ const _mask_brush_draw_callback = (evn, state) => {
|
||||||
maskPaintCtx.lineTo(evn.x, evn.y);
|
maskPaintCtx.lineTo(evn.x, evn.y);
|
||||||
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
||||||
maskPaintCtx.stroke();
|
maskPaintCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const _mask_brush_erase_callback = (evn, state) => {
|
const _mask_brush_erase_callback = (evn, state) => {
|
||||||
if (
|
|
||||||
(evn.initialTarget && evn.initialTarget.id === "overlayCanvas") ||
|
|
||||||
(!evn.initialTarget && evn.target.id === "overlayCanvas")
|
|
||||||
) {
|
|
||||||
maskPaintCtx.globalCompositeOperation = "destination-out";
|
maskPaintCtx.globalCompositeOperation = "destination-out";
|
||||||
maskPaintCtx.strokeStyle = "black";
|
maskPaintCtx.strokeStyle = "black";
|
||||||
|
|
||||||
|
@ -59,7 +50,6 @@ const _mask_brush_erase_callback = (evn, state) => {
|
||||||
maskPaintCtx.lineTo(evn.x, evn.y);
|
maskPaintCtx.lineTo(evn.x, evn.y);
|
||||||
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
||||||
maskPaintCtx.stroke();
|
maskPaintCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const maskBrushTool = () =>
|
const maskBrushTool = () =>
|
||||||
|
@ -69,27 +59,27 @@ const maskBrushTool = () =>
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
|
state.movecb({...mouse.coords.world.pos});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.movecb);
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||||||
mouse.listen.canvas.onwheel.on(state.wheelcb);
|
mouse.listen.world.onwheel.on(state.wheelcb);
|
||||||
mouse.listen.canvas.btn.left.onpaintstart.on(state.drawcb);
|
mouse.listen.world.btn.left.onpaintstart.on(state.drawcb);
|
||||||
mouse.listen.canvas.btn.left.onpaint.on(state.drawcb);
|
mouse.listen.world.btn.left.onpaint.on(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onpaintstart.on(state.erasecb);
|
mouse.listen.world.btn.right.onpaintstart.on(state.erasecb);
|
||||||
mouse.listen.canvas.btn.right.onpaint.on(state.erasecb);
|
mouse.listen.world.btn.right.onpaint.on(state.erasecb);
|
||||||
|
|
||||||
// Display Mask
|
// Display Mask
|
||||||
setMask("neutral");
|
setMask("neutral");
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||||
mouse.listen.canvas.onwheel.clear(state.wheelcb);
|
mouse.listen.world.onwheel.clear(state.wheelcb);
|
||||||
mouse.listen.canvas.btn.left.onpaintstart.clear(state.drawcb);
|
mouse.listen.world.btn.left.onpaintstart.clear(state.drawcb);
|
||||||
mouse.listen.canvas.btn.left.onpaint.clear(state.drawcb);
|
mouse.listen.world.btn.left.onpaint.clear(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onpaintstart.clear(state.erasecb);
|
mouse.listen.world.btn.right.onpaintstart.clear(state.erasecb);
|
||||||
mouse.listen.canvas.btn.right.onpaint.clear(state.erasecb);
|
mouse.listen.world.btn.right.onpaint.clear(state.erasecb);
|
||||||
|
|
||||||
// Hide Mask
|
// Hide Mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
@ -115,8 +105,8 @@ const maskBrushTool = () =>
|
||||||
state.preview = false;
|
state.preview = false;
|
||||||
|
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
// draw big translucent white blob cursor
|
// draw big translucent white blob cursor
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
ovCtx.beginPath();
|
ovCtx.beginPath();
|
||||||
ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line???
|
ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line???
|
||||||
ovCtx.fillStyle = "#FFFFFF50";
|
ovCtx.fillStyle = "#FFFFFF50";
|
||||||
|
@ -129,11 +119,10 @@ const maskBrushTool = () =>
|
||||||
ovCtx.stroke();
|
ovCtx.stroke();
|
||||||
ovCtx.setLineDash([]);
|
ovCtx.setLineDash([]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.wheelcb = (evn) => {
|
state.wheelcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
if (!evn.evn.ctrlKey) {
|
||||||
state.brushSize = state.setBrushSize(
|
state.brushSize = state.setBrushSize(
|
||||||
state.brushSize -
|
state.brushSize -
|
||||||
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
||||||
|
|
|
@ -5,16 +5,16 @@ const selectTransformTool = () =>
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
|
state.movecb(mouse.coords.world.pos);
|
||||||
|
|
||||||
// Canvas left mouse handlers
|
// Canvas left mouse handlers
|
||||||
mouse.listen.canvas.onmousemove.on(state.movecb);
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.clickcb);
|
mouse.listen.world.btn.left.onclick.on(state.clickcb);
|
||||||
mouse.listen.canvas.btn.left.ondragstart.on(state.dragstartcb);
|
mouse.listen.world.btn.left.ondragstart.on(state.dragstartcb);
|
||||||
mouse.listen.canvas.btn.left.ondragend.on(state.dragendcb);
|
mouse.listen.world.btn.left.ondragend.on(state.dragendcb);
|
||||||
|
|
||||||
// Canvas right mouse handler
|
// Canvas right mouse handler
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.on(state.cancelcb);
|
||||||
|
|
||||||
// Keyboard click handlers
|
// Keyboard click handlers
|
||||||
keyboard.listen.onkeyclick.on(state.keyclickcb);
|
keyboard.listen.onkeyclick.on(state.keyclickcb);
|
||||||
|
@ -29,12 +29,12 @@ const selectTransformTool = () =>
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear all those listeners and shortcuts we set up
|
// Clear all those listeners and shortcuts we set up
|
||||||
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.clickcb);
|
mouse.listen.world.btn.left.onclick.clear(state.clickcb);
|
||||||
mouse.listen.canvas.btn.left.ondragstart.clear(state.dragstartcb);
|
mouse.listen.world.btn.left.ondragstart.clear(state.dragstartcb);
|
||||||
mouse.listen.canvas.btn.left.ondragend.clear(state.dragendcb);
|
mouse.listen.world.btn.left.ondragend.clear(state.dragendcb);
|
||||||
|
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.clear(state.cancelcb);
|
||||||
|
|
||||||
keyboard.listen.onkeyclick.clear(state.keyclickcb);
|
keyboard.listen.onkeyclick.clear(state.keyclickcb);
|
||||||
keyboard.listen.onkeydown.clear(state.keydowncb);
|
keyboard.listen.onkeydown.clear(state.keydowncb);
|
||||||
|
@ -46,7 +46,7 @@ const selectTransformTool = () =>
|
||||||
state.reset();
|
state.reset();
|
||||||
|
|
||||||
// Resets cursor
|
// Resets cursor
|
||||||
ovCanvas.style.cursor = "auto";
|
imageCollection.inputElement.style.cursor = "auto";
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
@ -185,10 +185,10 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
// Mouse move handelr. As always, also renders cursor
|
// Mouse move handelr. As always, also renders cursor
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
ovCanvas.style.cursor = "auto";
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
imageCollection.inputElement.style.cursor = "auto";
|
||||||
state.lastMouseTarget = evn.target;
|
state.lastMouseTarget = evn.target;
|
||||||
state.lastMouseMove = evn;
|
state.lastMouseMove = evn;
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -281,7 +281,7 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
// Change cursor
|
// Change cursor
|
||||||
if (cursorInHandle || state.selected.contains(evn.x, evn.y))
|
if (cursorInHandle || state.selected.contains(evn.x, evn.y))
|
||||||
ovCanvas.style.cursor = "pointer";
|
imageCollection.inputElement.style.cursor = "pointer";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw current cursor location
|
// Draw current cursor location
|
||||||
|
@ -294,12 +294,10 @@ const selectTransformTool = () =>
|
||||||
ovCtx.moveTo(x + 10, y);
|
ovCtx.moveTo(x + 10, y);
|
||||||
ovCtx.lineTo(x - 10, y);
|
ovCtx.lineTo(x - 10, y);
|
||||||
ovCtx.stroke();
|
ovCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles left mouse clicks
|
// Handles left mouse clicks
|
||||||
state.clickcb = (evn) => {
|
state.clickcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
// If something is selected, commit changes to the canvas
|
// If something is selected, commit changes to the canvas
|
||||||
if (state.selected) {
|
if (state.selected) {
|
||||||
imgCtx.drawImage(
|
imgCtx.drawImage(
|
||||||
|
@ -322,12 +320,10 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles left mouse drag events
|
// Handles left mouse drag events
|
||||||
state.dragstartcb = (evn) => {
|
state.dragstartcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let ix = evn.ix;
|
let ix = evn.ix;
|
||||||
let iy = evn.iy;
|
let iy = evn.iy;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -355,12 +351,10 @@ const selectTransformTool = () =>
|
||||||
// If it is not, just create new selection
|
// If it is not, just create new selection
|
||||||
state.reset();
|
state.reset();
|
||||||
state.dragging = {ix, iy};
|
state.dragging = {ix, iy};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles left mouse drag end events
|
// Handles left mouse drag end events
|
||||||
state.dragendcb = (evn) => {
|
state.dragendcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -426,41 +420,31 @@ const selectTransformTool = () =>
|
||||||
state.dragging = null;
|
state.dragging = null;
|
||||||
}
|
}
|
||||||
redraw();
|
redraw();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handler for right clicks. Basically resets everything
|
// Handler for right clicks. Basically resets everything
|
||||||
state.cancelcb = (evn) => {
|
state.cancelcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
state.reset();
|
state.reset();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keyboard callbacks (For now, they just handle the "delete" key)
|
// Keyboard callbacks (For now, they just handle the "delete" key)
|
||||||
state.keydowncb = (evn) => {};
|
state.keydowncb = (evn) => {};
|
||||||
|
|
||||||
state.keyclickcb = (evn) => {
|
state.keyclickcb = (evn) => {
|
||||||
if (state.lastMouseTarget.id === "overlayCanvas") {
|
|
||||||
switch (evn.code) {
|
switch (evn.code) {
|
||||||
case "Delete":
|
case "Delete":
|
||||||
// Deletes selected area
|
// Deletes selected area
|
||||||
state.selected &&
|
state.selected &&
|
||||||
commands.runCommand(
|
commands.runCommand("eraseImage", "Erase Area", state.selected);
|
||||||
"eraseImage",
|
|
||||||
"Erase Area",
|
|
||||||
state.selected
|
|
||||||
);
|
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register Ctrl-C/V Shortcut
|
// Register Ctrl-C/V Shortcut
|
||||||
|
|
||||||
// Handles copying
|
// Handles copying
|
||||||
state.ctrlccb = (evn, cut = false) => {
|
state.ctrlccb = (evn, cut = false) => {
|
||||||
if (state.selected && state.lastMouseTarget.id === "overlayCanvas") {
|
|
||||||
// We create a new canvas to store the data
|
// We create a new canvas to store the data
|
||||||
state.clipboard.copy = document.createElement("canvas");
|
state.clipboard.copy = document.createElement("canvas");
|
||||||
|
|
||||||
|
@ -501,7 +485,6 @@ const selectTransformTool = () =>
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles pasting
|
// Handles pasting
|
||||||
|
|
|
@ -5,12 +5,12 @@ const stampTool = () =>
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
|
state.movecb({...mouse.coords.world.pos});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.movecb);
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.drawcb);
|
mouse.listen.world.btn.left.onclick.on(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.on(state.cancelcb);
|
||||||
|
|
||||||
// For calls from other tools to paste image
|
// For calls from other tools to paste image
|
||||||
if (opt && opt.image) {
|
if (opt && opt.image) {
|
||||||
|
@ -32,9 +32,9 @@ const stampTool = () =>
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.drawcb);
|
mouse.listen.world.btn.left.onclick.clear(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.clear(state.cancelcb);
|
||||||
|
|
||||||
// Deselect
|
// Deselect
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
|
@ -153,7 +153,6 @@ const stampTool = () =>
|
||||||
};
|
};
|
||||||
|
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
if (evn.target && evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -163,6 +162,8 @@ const stampTool = () =>
|
||||||
|
|
||||||
state.lastMouseMove = evn;
|
state.lastMouseMove = evn;
|
||||||
|
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
|
||||||
// Draw selected image
|
// Draw selected image
|
||||||
if (state.selected) {
|
if (state.selected) {
|
||||||
ovCtx.drawImage(state.selected.image, x, y);
|
ovCtx.drawImage(state.selected.image, x, y);
|
||||||
|
@ -178,11 +179,9 @@ const stampTool = () =>
|
||||||
ovCtx.moveTo(x + 10, y);
|
ovCtx.moveTo(x + 10, y);
|
||||||
ovCtx.lineTo(x - 10, y);
|
ovCtx.lineTo(x - 10, y);
|
||||||
ovCtx.stroke();
|
ovCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.drawcb = (evn) => {
|
state.drawcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -208,10 +207,8 @@ const stampTool = () =>
|
||||||
state.back = null;
|
state.back = null;
|
||||||
backfn({message: "Returning from stamp", pasted: true});
|
backfn({message: "Returning from stamp", pasted: true});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
state.cancelcb = (evn) => {
|
state.cancelcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
state.selectResource(null);
|
state.selectResource(null);
|
||||||
|
|
||||||
if (state.back) {
|
if (state.back) {
|
||||||
|
@ -220,7 +217,6 @@ const stampTool = () =>
|
||||||
state.back = null;
|
state.back = null;
|
||||||
backfn({message: "Returning from stamp", pasted: false});
|
backfn({message: "Returning from stamp", pasted: false});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
52
js/util.d.ts
vendored
52
js/util.d.ts
vendored
|
@ -1,52 +0,0 @@
|
||||||
/**
|
|
||||||
* Generates a random string in the following format:
|
|
||||||
*
|
|
||||||
* xxxx-xxxx-xxxx-...-xxxx
|
|
||||||
*
|
|
||||||
* @param size number of character quartets to generate
|
|
||||||
* @return Generated ID
|
|
||||||
*/
|
|
||||||
declare function guid(size: number): string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets default values for options parameters
|
|
||||||
*
|
|
||||||
* @param options An object received as a parameter
|
|
||||||
* @param defaults An object with default values for each expected key
|
|
||||||
* @return The original options parameter
|
|
||||||
*/
|
|
||||||
declare function defaultOpt(
|
|
||||||
options: {[key: string]: any},
|
|
||||||
defaults: {[key: string]: any}
|
|
||||||
): {[key: string]: any};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets default values for options parameters
|
|
||||||
*
|
|
||||||
* @param options An object received as a parameter
|
|
||||||
* @param defaults An object with default values for each expected key
|
|
||||||
* @return The original options parameter
|
|
||||||
*/
|
|
||||||
declare function makeReadOnly(
|
|
||||||
options: {[key: string]: any},
|
|
||||||
defaults: {[key: string]: any}
|
|
||||||
): {[key: string]: any};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes an object read-only, throwing an exception when attempting to set
|
|
||||||
*
|
|
||||||
* @param obj Object to be proxied
|
|
||||||
* @param name Name of the object, for logging purposes
|
|
||||||
* @return The proxied object
|
|
||||||
*/
|
|
||||||
declare function makeReadOnly(obj: object, name?: string): object;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes an object have each key be writeable only once, throwing an exception when
|
|
||||||
* attempting to set an existing parameter
|
|
||||||
*
|
|
||||||
* @param obj Object to be proxied
|
|
||||||
* @param name Name of the object, for logging purposes
|
|
||||||
* @return The proxied object
|
|
||||||
*/
|
|
||||||
declare function makeWriteOnce(obj: object, name?: string): object;
|
|
Loading…
Reference in a new issue