Merge branch 'main' into clear_brush_mask_option
This commit is contained in:
commit
01dbafb17f
14 changed files with 381 additions and 97 deletions
|
@ -318,6 +318,7 @@
|
||||||
type="text/javascript"></script>
|
type="text/javascript"></script>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
|
<script src="js/global.js" type="text/javascript"></script>
|
||||||
<script src="js/prompt.js" type="text/javascript"></script>
|
<script src="js/prompt.js" type="text/javascript"></script>
|
||||||
<script src="js/index.js" type="text/javascript"></script>
|
<script src="js/index.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
|
22
js/global.js
Normal file
22
js/global.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Stores global variables without polluting the global namespace.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const global = {
|
||||||
|
// Connection
|
||||||
|
_connection: "offline",
|
||||||
|
set connection(v) {
|
||||||
|
this._connection = v;
|
||||||
|
|
||||||
|
toolbar &&
|
||||||
|
toolbar.currentTool &&
|
||||||
|
toolbar.currentTool.state.redraw &&
|
||||||
|
toolbar.currentTool.state.redraw();
|
||||||
|
},
|
||||||
|
get connection() {
|
||||||
|
return this._connection;
|
||||||
|
},
|
||||||
|
|
||||||
|
// If there is a selected input
|
||||||
|
hasActiveInput: false,
|
||||||
|
};
|
34
js/index.js
34
js/index.js
|
@ -188,6 +188,7 @@ function startup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFixedHost(h, changePromptMessage) {
|
function setFixedHost(h, changePromptMessage) {
|
||||||
|
console.info(`[index] Fixed host to '${h}'`);
|
||||||
const hostInput = document.getElementById("host");
|
const hostInput = document.getElementById("host");
|
||||||
host = h;
|
host = h;
|
||||||
hostInput.value = h;
|
hostInput.value = h;
|
||||||
|
@ -340,7 +341,11 @@ async function testHostConnection() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
statuses[status] && statuses[status]();
|
statuses[status] &&
|
||||||
|
(() => {
|
||||||
|
statuses[status]();
|
||||||
|
global.connection = status;
|
||||||
|
})();
|
||||||
};
|
};
|
||||||
|
|
||||||
setConnectionStatus("before");
|
setConnectionStatus("before");
|
||||||
|
@ -411,7 +416,7 @@ async function testHostConnection() {
|
||||||
return status;
|
return status;
|
||||||
};
|
};
|
||||||
|
|
||||||
await checkConnection(true);
|
await checkConnection(!urlParams.has("noprompt"));
|
||||||
|
|
||||||
// On click, attempt to refresh
|
// On click, attempt to refresh
|
||||||
connectionIndicator.onclick = async () => {
|
connectionIndicator.onclick = async () => {
|
||||||
|
@ -457,6 +462,8 @@ function clearPaintedMask() {
|
||||||
|
|
||||||
function march(bb, options = {}) {
|
function march(bb, options = {}) {
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
|
title: null,
|
||||||
|
titleStyle: "#FFF5",
|
||||||
style: "#FFFF",
|
style: "#FFFF",
|
||||||
width: "2px",
|
width: "2px",
|
||||||
filter: null,
|
filter: null,
|
||||||
|
@ -471,6 +478,7 @@ function march(bb, options = {}) {
|
||||||
// Get temporary layer to draw marching ants
|
// Get temporary layer to draw marching ants
|
||||||
const layer = imageCollection.registerLayer(null, {
|
const layer = imageCollection.registerLayer(null, {
|
||||||
bb: expanded,
|
bb: expanded,
|
||||||
|
category: "display",
|
||||||
});
|
});
|
||||||
layer.canvas.style.imageRendering = "pixelated";
|
layer.canvas.style.imageRendering = "pixelated";
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
@ -490,6 +498,16 @@ function drawMarchingAnts(ctx, bb, offset, options) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
ctx.clearRect(0, 0, bb.w + 2, bb.h + 2);
|
ctx.clearRect(0, 0, bb.w + 2, bb.h + 2);
|
||||||
|
|
||||||
|
// Draw Tool Name
|
||||||
|
if (bb.h > 40 && options.title) {
|
||||||
|
ctx.font = `bold 20px Open Sans`;
|
||||||
|
|
||||||
|
ctx.textAlign = "left";
|
||||||
|
ctx.fillStyle = options.titleStyle;
|
||||||
|
ctx.fillText(options.title, 10, 30, bb.w);
|
||||||
|
}
|
||||||
|
|
||||||
ctx.strokeStyle = options.style;
|
ctx.strokeStyle = options.style;
|
||||||
ctx.strokeWidth = options.width;
|
ctx.strokeWidth = options.width;
|
||||||
ctx.filter = options.filter;
|
ctx.filter = options.filter;
|
||||||
|
@ -920,6 +938,12 @@ async function getSamplers() {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
|
samplerAutoComplete.onchange.on(({value}) => {
|
||||||
|
stableDiffusionData.sampler_index = value;
|
||||||
|
localStorage.setItem("openoutpaint/sampler", value);
|
||||||
|
});
|
||||||
|
|
||||||
samplerAutoComplete.options = data.map((sampler) => ({
|
samplerAutoComplete.options = data.map((sampler) => ({
|
||||||
name: sampler.name,
|
name: sampler.name,
|
||||||
value: sampler.name,
|
value: sampler.name,
|
||||||
|
@ -932,11 +956,7 @@ async function getSamplers() {
|
||||||
samplerAutoComplete.value = data[0].name;
|
samplerAutoComplete.value = data[0].name;
|
||||||
localStorage.setItem("openoutpaint/sampler", samplerAutoComplete.value);
|
localStorage.setItem("openoutpaint/sampler", samplerAutoComplete.value);
|
||||||
}
|
}
|
||||||
|
stableDiffusionData.sampler_index = samplerAutoComplete.value;
|
||||||
samplerAutoComplete.onchange.on(({value}) => {
|
|
||||||
stableDiffusionData.sampler_index = value;
|
|
||||||
localStorage.setItem("openoutpaint/sampler", value);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("[index] Failed to fetch samplers");
|
console.warn("[index] Failed to fetch samplers");
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
|
|
@ -20,20 +20,25 @@ const imageCollection = layers.registerCollection(
|
||||||
|
|
||||||
const bgLayer = imageCollection.registerLayer("bg", {
|
const bgLayer = imageCollection.registerLayer("bg", {
|
||||||
name: "Background",
|
name: "Background",
|
||||||
|
category: "background",
|
||||||
});
|
});
|
||||||
const imgLayer = imageCollection.registerLayer("image", {
|
const imgLayer = imageCollection.registerLayer("image", {
|
||||||
name: "Image",
|
name: "Image",
|
||||||
|
category: "image",
|
||||||
ctxOptions: {desynchronized: true},
|
ctxOptions: {desynchronized: true},
|
||||||
});
|
});
|
||||||
const maskPaintLayer = imageCollection.registerLayer("mask", {
|
const maskPaintLayer = imageCollection.registerLayer("mask", {
|
||||||
name: "Mask Paint",
|
name: "Mask Paint",
|
||||||
|
category: "mask",
|
||||||
ctxOptions: {desynchronized: true},
|
ctxOptions: {desynchronized: true},
|
||||||
});
|
});
|
||||||
const ovLayer = imageCollection.registerLayer("overlay", {
|
const ovLayer = imageCollection.registerLayer("overlay", {
|
||||||
name: "Overlay",
|
name: "Overlay",
|
||||||
|
category: "display",
|
||||||
});
|
});
|
||||||
const debugLayer = imageCollection.registerLayer("debug", {
|
const debugLayer = imageCollection.registerLayer("debug", {
|
||||||
name: "Debug Layer",
|
name: "Debug Layer",
|
||||||
|
category: "display",
|
||||||
});
|
});
|
||||||
|
|
||||||
const imgCanvas = imgLayer.canvas; // where dreams go
|
const imgCanvas = imgLayer.canvas; // where dreams go
|
||||||
|
@ -237,9 +242,28 @@ mouse.registerContext(
|
||||||
ctx.coords.pos.x = Math.round(layerCoords.x);
|
ctx.coords.pos.x = Math.round(layerCoords.x);
|
||||||
ctx.coords.pos.y = Math.round(layerCoords.y);
|
ctx.coords.pos.y = Math.round(layerCoords.y);
|
||||||
},
|
},
|
||||||
{target: imageCollection.inputElement}
|
{
|
||||||
|
target: imageCollection.inputElement,
|
||||||
|
validate: (evn) => {
|
||||||
|
if (!global.hasActiveInput || evn.type === "mousemove") return true;
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Redraw on active input state change
|
||||||
|
(() => {
|
||||||
|
mouse.listen.window.onany.on((evn) => {
|
||||||
|
const activeInput = DOM.hasActiveInput();
|
||||||
|
if (global.hasActiveInput !== activeInput) {
|
||||||
|
global.hasActiveInput = activeInput;
|
||||||
|
toolbar.currentTool &&
|
||||||
|
toolbar.currentTool.state.redraw &&
|
||||||
|
toolbar.currentTool.state.redraw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
mouse.listen.window.onwheel.on((evn) => {
|
mouse.listen.window.onwheel.on((evn) => {
|
||||||
if (evn.evn.ctrlKey) {
|
if (evn.evn.ctrlKey) {
|
||||||
evn.evn.preventDefault();
|
evn.evn.preventDefault();
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
* An object for mouse event listeners
|
* An object for mouse event listeners
|
||||||
*
|
*
|
||||||
* @typedef MouseListenerContext
|
* @typedef MouseListenerContext
|
||||||
|
* @property {Observer} onany A listener for any mouse events
|
||||||
* @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 {Record<string, MouseListenerBtnContext>} btn Button handlers
|
* @property {Record<string, MouseListenerBtnContext>} btn Button handlers
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
* @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 {(evn) => void} onany A function to be run on any event
|
||||||
* @property {?HTMLElement} target The target
|
* @property {?HTMLElement} target The target
|
||||||
|
* @property {(evn) => boolean} validate A function to be check if we will process an event
|
||||||
* @property {MouseCoordContext} coords Coordinates object
|
* @property {MouseCoordContext} coords Coordinates object
|
||||||
* @property {MouseListenerContext} listen Listeners object
|
* @property {MouseListenerContext} listen Listeners object
|
||||||
*/
|
*/
|
||||||
|
|
112
js/lib/input.js
112
js/lib/input.js
|
@ -63,16 +63,16 @@ const mouse = {
|
||||||
* @param {ContextMoveTransformer} onmove The function to perform coordinate transform
|
* @param {ContextMoveTransformer} onmove The function to perform coordinate transform
|
||||||
* @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 {(evn: any) => boolean} [options.validate] Checks if we will process this event or not
|
||||||
* @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 = {}) => {
|
||||||
// Options
|
// Options
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
target: null,
|
target: null,
|
||||||
|
validate: () => true,
|
||||||
buttons: {0: "left", 1: "middle", 2: "right"},
|
buttons: {0: "left", 1: "middle", 2: "right"},
|
||||||
genericcb: null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Context information
|
// Context information
|
||||||
|
@ -81,8 +81,8 @@ const mouse = {
|
||||||
id: guid(),
|
id: guid(),
|
||||||
name,
|
name,
|
||||||
onmove,
|
onmove,
|
||||||
onany: options.genericcb,
|
|
||||||
target: options.target,
|
target: options.target,
|
||||||
|
validate: options.validate,
|
||||||
buttons: options.buttons,
|
buttons: options.buttons,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -102,12 +102,27 @@ const mouse = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listeners
|
// Listeners
|
||||||
|
const onany = new Observer();
|
||||||
|
|
||||||
mouse.listen[name] = {
|
mouse.listen[name] = {
|
||||||
|
onany,
|
||||||
onwheel: new Observer(),
|
onwheel: new Observer(),
|
||||||
onmousemove: new Observer(),
|
onmousemove: new Observer(),
|
||||||
btn: {},
|
btn: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Always process onany events first
|
||||||
|
mouse.listen[name].onwheel.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].onmousemove.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
// Button specific items
|
// Button specific items
|
||||||
Object.keys(options.buttons).forEach((index) => {
|
Object.keys(options.buttons).forEach((index) => {
|
||||||
const button = options.buttons[index];
|
const button = options.buttons[index];
|
||||||
|
@ -115,6 +130,48 @@ const mouse = {
|
||||||
mouse.listen[name].btn[button] = _mouse_observers(
|
mouse.listen[name].btn[button] = _mouse_observers(
|
||||||
`mouse.listen[${name}].btn[${button}]`
|
`mouse.listen[${name}].btn[${button}]`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Always process onany events first
|
||||||
|
mouse.listen[name].btn[button].onclick.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].btn[button].ondclick.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].btn[button].ondragstart.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].btn[button].ondrag.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].btn[button].ondragend.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].btn[button].onpaintstart.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].btn[button].onpaint.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mouse.listen[name].btn[button].onpaintend.on(
|
||||||
|
async (evn, state) => await onany.emit(evn, state),
|
||||||
|
Infinity,
|
||||||
|
true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add to context
|
// Add to context
|
||||||
|
@ -183,11 +240,13 @@ window.addEventListener(
|
||||||
|
|
||||||
mouse.buttons[evn.button] = time;
|
mouse.buttons[evn.button] = time;
|
||||||
|
|
||||||
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
mouse._contexts.forEach(({target, name, buttons, validate}) => {
|
||||||
const key = buttons[evn.button];
|
const key = buttons[evn.button];
|
||||||
if ((!target || target === evn.target) && key) {
|
if (
|
||||||
onany && onany();
|
(!target || target === evn.target) &&
|
||||||
|
key &&
|
||||||
|
(!validate || validate(evn))
|
||||||
|
) {
|
||||||
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);
|
||||||
|
@ -214,14 +273,14 @@ window.addEventListener(
|
||||||
(evn) => {
|
(evn) => {
|
||||||
const time = performance.now();
|
const time = performance.now();
|
||||||
|
|
||||||
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
mouse._contexts.forEach(({target, name, buttons, validate}) => {
|
||||||
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] &&
|
||||||
|
(!validate || validate(evn))
|
||||||
) {
|
) {
|
||||||
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,
|
||||||
|
@ -292,7 +351,10 @@ window.addEventListener(
|
||||||
const target = context.target;
|
const target = context.target;
|
||||||
const name = context.name;
|
const name = context.name;
|
||||||
|
|
||||||
if (!target || target === evn.target) {
|
if (
|
||||||
|
!target ||
|
||||||
|
(target === evn.target && (!context.validate || context.validate(evn)))
|
||||||
|
) {
|
||||||
context.onmove(evn, context);
|
context.onmove(evn, context);
|
||||||
|
|
||||||
mouse.listen[name].onmousemove.emit({
|
mouse.listen[name].onmousemove.emit({
|
||||||
|
@ -378,19 +440,21 @@ window.addEventListener(
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"wheel",
|
"wheel",
|
||||||
(evn) => {
|
(evn) => {
|
||||||
mouse._contexts.forEach(({name}) => {
|
mouse._contexts.forEach(({name, target, validate}) => {
|
||||||
mouse.listen[name].onwheel.emit({
|
if (!target || (target === evn.target && (!validate || validate(evn)))) {
|
||||||
target: evn.target,
|
mouse.listen[name].onwheel.emit({
|
||||||
delta: evn.deltaY,
|
target: evn.target,
|
||||||
deltaX: evn.deltaX,
|
delta: evn.deltaY,
|
||||||
deltaY: evn.deltaY,
|
deltaX: evn.deltaX,
|
||||||
deltaZ: evn.deltaZ,
|
deltaY: evn.deltaY,
|
||||||
mode: evn.deltaMode,
|
deltaZ: evn.deltaZ,
|
||||||
x: mouse.coords[name].pos.x,
|
mode: evn.deltaMode,
|
||||||
y: mouse.coords[name].pos.y,
|
x: mouse.coords[name].pos.x,
|
||||||
evn,
|
y: mouse.coords[name].pos.y,
|
||||||
timestamp: performance.now(),
|
evn,
|
||||||
});
|
timestamp: performance.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{passive: false}
|
{passive: false}
|
||||||
|
|
|
@ -224,9 +224,6 @@ const layers = {
|
||||||
// Input element (overlay element for input handling)
|
// Input element (overlay element for input handling)
|
||||||
const inputel = document.createElement("div");
|
const inputel = document.createElement("div");
|
||||||
inputel.id = `collection-input-${id}`;
|
inputel.id = `collection-input-${id}`;
|
||||||
inputel.addEventListener("mouseover", (evn) => {
|
|
||||||
document.activeElement.blur();
|
|
||||||
});
|
|
||||||
inputel.classList.add("collection-input-overlay");
|
inputel.classList.add("collection-input-overlay");
|
||||||
element.appendChild(inputel);
|
element.appendChild(inputel);
|
||||||
|
|
||||||
|
@ -340,6 +337,7 @@ const layers = {
|
||||||
* @param {object} options
|
* @param {object} options
|
||||||
* @param {string} options.name
|
* @param {string} options.name
|
||||||
* @param {?BoundingBox} options.bb
|
* @param {?BoundingBox} options.bb
|
||||||
|
* @param {string} [options.category]
|
||||||
* @param {{w: number, h: number}} options.resolution
|
* @param {{w: number, h: number}} options.resolution
|
||||||
* @param {?string} options.group
|
* @param {?string} options.group
|
||||||
* @param {object} options.after
|
* @param {object} options.after
|
||||||
|
@ -362,6 +360,9 @@ const layers = {
|
||||||
h: collection.size.h,
|
h: collection.size.h,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Category of the layer
|
||||||
|
category: null,
|
||||||
|
|
||||||
// Resolution for layer
|
// Resolution for layer
|
||||||
resolution: null,
|
resolution: null,
|
||||||
|
|
||||||
|
@ -451,6 +452,7 @@ const layers = {
|
||||||
key,
|
key,
|
||||||
name: options.name,
|
name: options.name,
|
||||||
full,
|
full,
|
||||||
|
category: options.category,
|
||||||
|
|
||||||
state: new Proxy(
|
state: new Proxy(
|
||||||
{visible: true},
|
{visible: true},
|
||||||
|
@ -494,6 +496,10 @@ const layers = {
|
||||||
return this._collection.origin;
|
return this._collection.origin;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get hidden() {
|
||||||
|
return !this.state.visible;
|
||||||
|
},
|
||||||
|
|
||||||
/** Our canvas */
|
/** Our canvas */
|
||||||
canvas,
|
canvas,
|
||||||
ctx,
|
ctx,
|
||||||
|
|
|
@ -106,9 +106,9 @@ class Observer {
|
||||||
* Sends a message to all observers
|
* Sends a message to all observers
|
||||||
*
|
*
|
||||||
* @param {T} msg The message to send to the observers
|
* @param {T} msg The message to send to the observers
|
||||||
|
* @param {any} state The initial state
|
||||||
*/
|
*/
|
||||||
async emit(msg) {
|
async emit(msg, state = {}) {
|
||||||
const state = {};
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (const {handler, wait} of this._handlers) {
|
for (const {handler, wait} of this._handlers) {
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
|
@ -128,6 +128,25 @@ class Observer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static DOM utility functions
|
||||||
|
*/
|
||||||
|
class DOM {
|
||||||
|
static inputTags = new Set(["input", "textarea"]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there is an active input
|
||||||
|
*
|
||||||
|
* @returns Whether there is currently an active input
|
||||||
|
*/
|
||||||
|
static hasActiveInput() {
|
||||||
|
return (
|
||||||
|
document.activeElement &&
|
||||||
|
this.inputTags.has(document.activeElement.tagName.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a simple UID in the format xxxx-xxxx-...-xxxx, with x being [0-9a-f]
|
* Generates a simple UID in the format xxxx-xxxx-...-xxxx, with x being [0-9a-f]
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,10 +3,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const uil = {
|
const uil = {
|
||||||
|
/** @type {Observer<{uilayer: UILayer}>} */
|
||||||
|
onactive: new Observer(),
|
||||||
|
|
||||||
_ui_layer_list: document.getElementById("layer-list"),
|
_ui_layer_list: document.getElementById("layer-list"),
|
||||||
layers: [],
|
layers: [],
|
||||||
_active: null,
|
_active: null,
|
||||||
set active(v) {
|
set active(v) {
|
||||||
|
this.onactive.emit({
|
||||||
|
uilayer: v,
|
||||||
|
});
|
||||||
|
|
||||||
Array.from(this._ui_layer_list.children).forEach((child) => {
|
Array.from(this._ui_layer_list.children).forEach((child) => {
|
||||||
child.classList.remove("active");
|
child.classList.remove("active");
|
||||||
});
|
});
|
||||||
|
@ -188,6 +195,7 @@ const uil = {
|
||||||
_addLayer(group, name) {
|
_addLayer(group, name) {
|
||||||
const layer = imageCollection.registerLayer(null, {
|
const layer = imageCollection.registerLayer(null, {
|
||||||
name,
|
name,
|
||||||
|
category: "user",
|
||||||
after:
|
after:
|
||||||
(this.layers.length > 0 && this.layers[this.layers.length - 1].layer) ||
|
(this.layers.length > 0 && this.layers[this.layers.length - 1].layer) ||
|
||||||
bgLayer,
|
bgLayer,
|
||||||
|
@ -285,11 +293,13 @@ const uil = {
|
||||||
* @param {BoundingBox} bb The bouding box to get visible data from
|
* @param {BoundingBox} bb The bouding box to get visible data from
|
||||||
* @param {object} [options] Options
|
* @param {object} [options] Options
|
||||||
* @param {boolean} [options.includeBg=false] Whether to include the background
|
* @param {boolean} [options.includeBg=false] Whether to include the background
|
||||||
|
* @param {string[]} [options.categories] Categories of layers to consider visible
|
||||||
* @returns {HTMLCanvasElement} The canvas element containing visible image data
|
* @returns {HTMLCanvasElement} The canvas element containing visible image data
|
||||||
*/
|
*/
|
||||||
getVisible(bb, options = {}) {
|
getVisible(bb, options = {}) {
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
includeBg: false,
|
includeBg: false,
|
||||||
|
categories: ["user", "image"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
|
@ -297,21 +307,14 @@ const uil = {
|
||||||
|
|
||||||
canvas.width = bb.w;
|
canvas.width = bb.w;
|
||||||
canvas.height = bb.h;
|
canvas.height = bb.h;
|
||||||
if (options.includeBg)
|
|
||||||
ctx.drawImage(bgLayer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
const categories = new Set(options.categories);
|
||||||
this.layers.forEach((layer) => {
|
if (options.includeBg) categories.add("background");
|
||||||
if (!layer.hidden)
|
const layers = imageCollection._layers;
|
||||||
ctx.drawImage(
|
|
||||||
layer.layer.canvas,
|
layers.reduceRight((_, layer) => {
|
||||||
bb.x,
|
if (categories.has(layer.category) && !layer.hidden)
|
||||||
bb.y,
|
ctx.drawImage(layer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
||||||
bb.w,
|
|
||||||
bb.h,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bb.w,
|
|
||||||
bb.h
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
|
@ -336,6 +339,7 @@ commands.createCommand(
|
||||||
|
|
||||||
const layer = imageCollection.registerLayer(null, {
|
const layer = imageCollection.registerLayer(null, {
|
||||||
name,
|
name,
|
||||||
|
category: "user",
|
||||||
after:
|
after:
|
||||||
(uil.layers.length > 0 && uil.layers[uil.layers.length - 1].layer) ||
|
(uil.layers.length > 0 && uil.layers[uil.layers.length - 1].layer) ||
|
||||||
bgLayer,
|
bgLayer,
|
||||||
|
|
|
@ -62,16 +62,19 @@ const colorBrushTool = () =>
|
||||||
|
|
||||||
state.drawLayer = imageCollection.registerLayer(null, {
|
state.drawLayer = imageCollection.registerLayer(null, {
|
||||||
after: imgLayer,
|
after: imgLayer,
|
||||||
|
category: "display",
|
||||||
ctxOptions: {willReadFrequently: true},
|
ctxOptions: {willReadFrequently: true},
|
||||||
});
|
});
|
||||||
state.drawLayer.canvas.style.filter = "opacity(70%)";
|
state.drawLayer.canvas.style.filter = "opacity(70%)";
|
||||||
state.eraseLayer = imageCollection.registerLayer(null, {
|
state.eraseLayer = imageCollection.registerLayer(null, {
|
||||||
after: imgLayer,
|
after: imgLayer,
|
||||||
|
category: "processing",
|
||||||
ctxOptions: {willReadFrequently: true},
|
ctxOptions: {willReadFrequently: true},
|
||||||
});
|
});
|
||||||
state.eraseLayer.hide();
|
state.eraseLayer.hide();
|
||||||
state.eraseBackup = imageCollection.registerLayer(null, {
|
state.eraseBackup = imageCollection.registerLayer(null, {
|
||||||
after: imgLayer,
|
after: imgLayer,
|
||||||
|
category: "processing",
|
||||||
});
|
});
|
||||||
state.eraseBackup.hide();
|
state.eraseBackup.hide();
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ const _monitorProgress = (bb, oncheck = null) => {
|
||||||
// Get temporary layer to draw progress bar
|
// Get temporary layer to draw progress bar
|
||||||
const layer = imageCollection.registerLayer(null, {
|
const layer = imageCollection.registerLayer(null, {
|
||||||
bb: expanded,
|
bb: expanded,
|
||||||
|
category: "display",
|
||||||
});
|
});
|
||||||
layer.canvas.style.opacity = "70%";
|
layer.canvas.style.opacity = "70%";
|
||||||
|
|
||||||
|
@ -293,6 +294,7 @@ const _generate = async (endpoint, request, bb, options = {}) => {
|
||||||
// Layer for the images
|
// Layer for the images
|
||||||
const layer = imageCollection.registerLayer(null, {
|
const layer = imageCollection.registerLayer(null, {
|
||||||
after: maskPaintLayer,
|
after: maskPaintLayer,
|
||||||
|
category: "display",
|
||||||
});
|
});
|
||||||
|
|
||||||
const redraw = (url = images[at]) => {
|
const redraw = (url = images[at]) => {
|
||||||
|
@ -1227,7 +1229,7 @@ const dreamTool = () =>
|
||||||
y += snap(evn.y, 0, 64);
|
y += snap(evn.y, 0, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.erasePrevReticle = _tool._cursor_draw(x, y);
|
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||||
|
|
||||||
if (state.selection.exists) {
|
if (state.selection.exists) {
|
||||||
const bb = state.selection.bb;
|
const bb = state.selection.bb;
|
||||||
|
@ -1251,6 +1253,8 @@ const dreamTool = () =>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
toolTextStyle:
|
||||||
|
global.connection === "online" ? "#FFF5" : "#F555",
|
||||||
reticleStyle: state.selection.inside ? "#F55" : "#FFF",
|
reticleStyle: state.selection.inside ? "#F55" : "#FFF",
|
||||||
sizeTextStyle: style,
|
sizeTextStyle: style,
|
||||||
}
|
}
|
||||||
|
@ -1278,6 +1282,7 @@ const dreamTool = () =>
|
||||||
h: stableDiffusionData.height,
|
h: stableDiffusionData.height,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
toolTextStyle: global.connection === "online" ? "#FFF5" : "#F555",
|
||||||
sizeTextStyle: style,
|
sizeTextStyle: style,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1306,8 +1311,19 @@ const dreamTool = () =>
|
||||||
w: stableDiffusionData.width,
|
w: stableDiffusionData.width,
|
||||||
h: stableDiffusionData.height,
|
h: stableDiffusionData.height,
|
||||||
};
|
};
|
||||||
dream_generate_callback(bb, resolution, state);
|
|
||||||
|
if (global.connection === "online") {
|
||||||
|
dream_generate_callback(bb, resolution, state);
|
||||||
|
} else {
|
||||||
|
const stop = march(bb, {
|
||||||
|
title: "offline",
|
||||||
|
titleStyle: "#F555",
|
||||||
|
style: "#F55",
|
||||||
|
});
|
||||||
|
setTimeout(stop, 2000);
|
||||||
|
}
|
||||||
state.selection.deselect();
|
state.selection.deselect();
|
||||||
|
state.redraw();
|
||||||
};
|
};
|
||||||
state.erasecb = (evn, estate) => {
|
state.erasecb = (evn, estate) => {
|
||||||
if (state.selection.exists) {
|
if (state.selection.exists) {
|
||||||
|
@ -1595,7 +1611,7 @@ const img2imgTool = () =>
|
||||||
y += snap(evn.y, 0, 64);
|
y += snap(evn.y, 0, 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.erasePrevReticle = _tool._cursor_draw(x, y);
|
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||||
|
|
||||||
// Resolution
|
// Resolution
|
||||||
let bb = null;
|
let bb = null;
|
||||||
|
@ -1624,6 +1640,8 @@ const img2imgTool = () =>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
toolTextStyle:
|
||||||
|
global.connection === "online" ? "#FFF5" : "#F555",
|
||||||
reticleStyle: state.selection.inside ? "#F55" : "#FFF",
|
reticleStyle: state.selection.inside ? "#F55" : "#FFF",
|
||||||
sizeTextStyle: style,
|
sizeTextStyle: style,
|
||||||
}
|
}
|
||||||
|
@ -1653,6 +1671,8 @@ const img2imgTool = () =>
|
||||||
"Img2Img",
|
"Img2Img",
|
||||||
{w: request.width, h: request.height},
|
{w: request.width, h: request.height},
|
||||||
{
|
{
|
||||||
|
toolTextStyle:
|
||||||
|
global.connection === "online" ? "#FFF5" : "#F555",
|
||||||
sizeTextStyle: style,
|
sizeTextStyle: style,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1777,7 +1797,16 @@ const img2imgTool = () =>
|
||||||
w: stableDiffusionData.width,
|
w: stableDiffusionData.width,
|
||||||
h: stableDiffusionData.height,
|
h: stableDiffusionData.height,
|
||||||
};
|
};
|
||||||
dream_img2img_callback(bb, resolution, state);
|
if (global.connection === "online") {
|
||||||
|
dream_img2img_callback(bb, resolution, state);
|
||||||
|
} else {
|
||||||
|
const stop = march(bb, {
|
||||||
|
title: "offline",
|
||||||
|
titleStyle: "#F555",
|
||||||
|
style: "#F55",
|
||||||
|
});
|
||||||
|
setTimeout(stop, 2000);
|
||||||
|
}
|
||||||
state.selection.deselect();
|
state.selection.deselect();
|
||||||
state.redraw();
|
state.redraw();
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ const _tool = {
|
||||||
* @param {string} [style.genSizeTextStyle = "#FFF5"] Style of the text for diplaying generation size
|
* @param {string} [style.genSizeTextStyle = "#FFF5"] Style of the text for diplaying generation size
|
||||||
* @param {string} [style.toolTextStyle = "#FFF5"] Style of the text for the tool name
|
* @param {string} [style.toolTextStyle = "#FFF5"] Style of the text for the tool name
|
||||||
* @param {number} [style.reticleWidth = 1] Width of the line of the reticle
|
* @param {number} [style.reticleWidth = 1] Width of the line of the reticle
|
||||||
* @param {string} [style.reticleStyle = "#FFF"] Style of the line of the reticle
|
* @param {string} [style.reticleStyle] Style of the line of the reticle
|
||||||
*
|
*
|
||||||
* @returns A function that erases this reticle drawing
|
* @returns A function that erases this reticle drawing
|
||||||
*/
|
*/
|
||||||
|
@ -24,7 +24,7 @@ const _tool = {
|
||||||
genSizeTextStyle: "#FFF5",
|
genSizeTextStyle: "#FFF5",
|
||||||
toolTextStyle: "#FFF5",
|
toolTextStyle: "#FFF5",
|
||||||
reticleWidth: 1,
|
reticleWidth: 1,
|
||||||
reticleStyle: "#FFF",
|
reticleStyle: global.hasActiveInput ? "#BBF" : "#FFF",
|
||||||
});
|
});
|
||||||
|
|
||||||
const bbvp = {
|
const bbvp = {
|
||||||
|
@ -110,14 +110,14 @@ const _tool = {
|
||||||
* @param {number} y Y world coordinate of the cursor
|
* @param {number} y Y world coordinate of the cursor
|
||||||
* @param {object} style Style of the lines of the cursor
|
* @param {object} style Style of the lines of the cursor
|
||||||
* @param {string} [style.width = 3] Line width of the lines of the cursor
|
* @param {string} [style.width = 3] Line width of the lines of the cursor
|
||||||
* @param {string} [style.style = "#FFF5"] Stroke style of the lines of the cursor
|
* @param {string} [style.style] Stroke style of the lines of the cursor
|
||||||
*
|
*
|
||||||
* @returns A function that erases this cursor drawing
|
* @returns A function that erases this cursor drawing
|
||||||
*/
|
*/
|
||||||
_cursor_draw(x, y, style = {}) {
|
_cursor_draw(x, y, style = {}) {
|
||||||
defaultOpt(style, {
|
defaultOpt(style, {
|
||||||
width: 3,
|
width: 3,
|
||||||
style: "#FFF5",
|
style: global.hasActiveInput ? "#BBF5" : "#FFF5",
|
||||||
});
|
});
|
||||||
const vpc = viewport.canvasToView(x, y);
|
const vpc = viewport.canvasToView(x, y);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,9 @@ const selectTransformTool = () =>
|
||||||
keyboard.listen.onkeyclick.on(state.keyclickcb);
|
keyboard.listen.onkeyclick.on(state.keyclickcb);
|
||||||
keyboard.listen.onkeydown.on(state.keydowncb);
|
keyboard.listen.onkeydown.on(state.keydowncb);
|
||||||
|
|
||||||
|
// Layer system handlers
|
||||||
|
uil.onactive.on(state.uilayeractivecb);
|
||||||
|
|
||||||
// Registers keyboard shortcuts
|
// Registers keyboard shortcuts
|
||||||
keyboard.onShortcut({ctrl: true, key: "KeyC"}, state.ctrlccb);
|
keyboard.onShortcut({ctrl: true, key: "KeyC"}, state.ctrlccb);
|
||||||
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
|
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
|
||||||
|
@ -42,6 +45,8 @@ const selectTransformTool = () =>
|
||||||
keyboard.deleteShortcut(state.ctrlvcb, "KeyV");
|
keyboard.deleteShortcut(state.ctrlvcb, "KeyV");
|
||||||
keyboard.deleteShortcut(state.ctrlxcb, "KeyX");
|
keyboard.deleteShortcut(state.ctrlxcb, "KeyX");
|
||||||
|
|
||||||
|
uil.onactive.clear(state.uilayeractivecb);
|
||||||
|
|
||||||
// Clear any selections
|
// Clear any selections
|
||||||
state.reset();
|
state.reset();
|
||||||
|
|
||||||
|
@ -61,6 +66,7 @@ const selectTransformTool = () =>
|
||||||
state.useClipboard = !!(
|
state.useClipboard = !!(
|
||||||
navigator.clipboard && navigator.clipboard.write
|
navigator.clipboard && navigator.clipboard.write
|
||||||
); // Use it by default if supported
|
); // Use it by default if supported
|
||||||
|
state.selectionPeekOpacity = 40;
|
||||||
|
|
||||||
state.original = null;
|
state.original = null;
|
||||||
state.dragging = null;
|
state.dragging = null;
|
||||||
|
@ -78,22 +84,33 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
// Some things to easy request for a redraw
|
// Some things to easy request for a redraw
|
||||||
state.lastMouseTarget = null;
|
state.lastMouseTarget = null;
|
||||||
state.lastMouseMove = null;
|
state.lastMouseMove = {x: 0, y: 0};
|
||||||
|
|
||||||
state.redraw = () => {
|
state.redraw = () => {
|
||||||
ovLayer.clear();
|
ovLayer.clear();
|
||||||
state.movecb(state.lastMouseMove);
|
state.movecb(state.lastMouseMove);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state.uilayeractivecb = ({uilayer}) => {
|
||||||
|
if (state.originalDisplayLayer) {
|
||||||
|
state.originalDisplayLayer.moveAfter(uilayer.layer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Clears selection and make things right
|
// Clears selection and make things right
|
||||||
state.reset = () => {
|
state.reset = (erase = false) => {
|
||||||
if (state.selected)
|
if (state.selected && !erase)
|
||||||
uil.ctx.drawImage(
|
state.originalLayer.ctx.drawImage(
|
||||||
state.original.image,
|
state.original.image,
|
||||||
state.original.x,
|
state.original.x,
|
||||||
state.original.y
|
state.original.y
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (state.originalDisplayLayer) {
|
||||||
|
imageCollection.deleteLayer(state.originalDisplayLayer);
|
||||||
|
state.originalDisplayLayer = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (state.dragging) state.dragging = null;
|
if (state.dragging) state.dragging = null;
|
||||||
else state.selected = null;
|
else state.selected = null;
|
||||||
|
|
||||||
|
@ -189,6 +206,7 @@ const selectTransformTool = () =>
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
ovLayer.clear();
|
ovLayer.clear();
|
||||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
|
state.erasePrevCursor && state.erasePrevCursor();
|
||||||
imageCollection.inputElement.style.cursor = "auto";
|
imageCollection.inputElement.style.cursor = "auto";
|
||||||
state.lastMouseTarget = evn.target;
|
state.lastMouseTarget = evn.target;
|
||||||
state.lastMouseMove = evn;
|
state.lastMouseMove = evn;
|
||||||
|
@ -254,6 +272,8 @@ const selectTransformTool = () =>
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw Image
|
// Draw Image
|
||||||
|
ovCtx.save();
|
||||||
|
ovCtx.filter = `opacity(${state.selectionPeekOpacity}%)`;
|
||||||
ovCtx.drawImage(
|
ovCtx.drawImage(
|
||||||
state.selected.image,
|
state.selected.image,
|
||||||
0,
|
0,
|
||||||
|
@ -265,6 +285,22 @@ const selectTransformTool = () =>
|
||||||
state.selected.w,
|
state.selected.w,
|
||||||
state.selected.h
|
state.selected.h
|
||||||
);
|
);
|
||||||
|
ovCtx.restore();
|
||||||
|
|
||||||
|
state.originalDisplayLayer.clear();
|
||||||
|
state.originalDisplayLayer.ctx.save();
|
||||||
|
state.originalDisplayLayer.ctx.drawImage(
|
||||||
|
state.selected.image,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
state.selected.image.width,
|
||||||
|
state.selected.image.height,
|
||||||
|
state.selected.x,
|
||||||
|
state.selected.y,
|
||||||
|
state.selected.w,
|
||||||
|
state.selected.h
|
||||||
|
);
|
||||||
|
state.originalDisplayLayer.ctx.restore();
|
||||||
|
|
||||||
// Draw selection box
|
// Draw selection box
|
||||||
uiCtx.strokeStyle = "#FFF";
|
uiCtx.strokeStyle = "#FFF";
|
||||||
|
@ -320,15 +356,7 @@ const selectTransformTool = () =>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw current cursor location
|
// Draw current cursor location
|
||||||
uiCtx.lineWidth = 3;
|
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||||
uiCtx.strokeStyle = "#FFF";
|
|
||||||
|
|
||||||
uiCtx.beginPath();
|
|
||||||
uiCtx.moveTo(vpc.x, vpc.y + 10);
|
|
||||||
uiCtx.lineTo(vpc.x, vpc.y - 10);
|
|
||||||
uiCtx.moveTo(vpc.x + 10, vpc.y);
|
|
||||||
uiCtx.lineTo(vpc.x - 10, vpc.y);
|
|
||||||
uiCtx.stroke();
|
|
||||||
|
|
||||||
uiCtx.restore();
|
uiCtx.restore();
|
||||||
};
|
};
|
||||||
|
@ -337,7 +365,8 @@ const selectTransformTool = () =>
|
||||||
state.clickcb = (evn) => {
|
state.clickcb = (evn) => {
|
||||||
if (
|
if (
|
||||||
!state.original ||
|
!state.original ||
|
||||||
(state.original.x === state.selected.x &&
|
(state.originalLayer === uil.layer &&
|
||||||
|
state.original.x === state.selected.x &&
|
||||||
state.original.y === state.selected.y &&
|
state.original.y === state.selected.y &&
|
||||||
state.original.w === state.selected.w &&
|
state.original.w === state.selected.w &&
|
||||||
state.original.h === state.selected.h)
|
state.original.h === state.selected.h)
|
||||||
|
@ -348,16 +377,15 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
// If something is selected, commit changes to the canvas
|
// If something is selected, commit changes to the canvas
|
||||||
if (state.selected) {
|
if (state.selected) {
|
||||||
uil.ctx.drawImage(
|
state.originalLayer.ctx.drawImage(
|
||||||
state.selected.image,
|
state.selected.image,
|
||||||
state.original.x,
|
state.original.x,
|
||||||
state.original.y
|
state.original.y
|
||||||
);
|
);
|
||||||
commands.runCommand(
|
commands.runCommand("eraseImage", "Image Transform Erase", {
|
||||||
"eraseImage",
|
...state.original,
|
||||||
"Image Transform Erase",
|
ctx: state.originalLayer.ctx,
|
||||||
state.original
|
});
|
||||||
);
|
|
||||||
commands.runCommand("drawImage", "Image Transform Draw", {
|
commands.runCommand("drawImage", "Image Transform Draw", {
|
||||||
image: state.selected.image,
|
image: state.selected.image,
|
||||||
x: Math.round(state.selected.x),
|
x: Math.round(state.selected.x),
|
||||||
|
@ -365,10 +393,7 @@ const selectTransformTool = () =>
|
||||||
w: Math.round(state.selected.w),
|
w: Math.round(state.selected.w),
|
||||||
h: Math.round(state.selected.h),
|
h: Math.round(state.selected.h),
|
||||||
});
|
});
|
||||||
state.original = null;
|
state.reset(true);
|
||||||
state.selected = null;
|
|
||||||
|
|
||||||
state.redraw();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -451,6 +476,11 @@ const selectTransformTool = () =>
|
||||||
x,
|
x,
|
||||||
y
|
y
|
||||||
);
|
);
|
||||||
|
state.originalLayer = uil.layer;
|
||||||
|
state.originalDisplayLayer = imageCollection.registerLayer(null, {
|
||||||
|
after: uil.layer,
|
||||||
|
category: "select-display",
|
||||||
|
});
|
||||||
|
|
||||||
// Cut out selected portion of the image for manipulation
|
// Cut out selected portion of the image for manipulation
|
||||||
const cvs = document.createElement("canvas");
|
const cvs = document.createElement("canvas");
|
||||||
|
@ -622,6 +652,22 @@ const selectTransformTool = () =>
|
||||||
if (!(navigator.clipboard && navigator.clipboard.write))
|
if (!(navigator.clipboard && navigator.clipboard.write))
|
||||||
clipboardCheckbox.checkbox.disabled = true; // Disable if not available
|
clipboardCheckbox.checkbox.disabled = true; // Disable if not available
|
||||||
|
|
||||||
|
// Selection Peek Opacity
|
||||||
|
state.ctxmenu.selectionPeekOpacitySlider = _toolbar_input.slider(
|
||||||
|
state,
|
||||||
|
"selectionPeekOpacity",
|
||||||
|
"Peek Opacity",
|
||||||
|
{
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 10,
|
||||||
|
textStep: 1,
|
||||||
|
cb: () => {
|
||||||
|
state.redraw();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
).slider;
|
||||||
|
|
||||||
// Some useful actions to do with selection
|
// Some useful actions to do with selection
|
||||||
const actionArray = document.createElement("div");
|
const actionArray = document.createElement("div");
|
||||||
actionArray.classList.add("button-array");
|
actionArray.classList.add("button-array");
|
||||||
|
@ -629,7 +675,7 @@ const selectTransformTool = () =>
|
||||||
// Save button
|
// Save button
|
||||||
const saveSelectionButton = document.createElement("button");
|
const saveSelectionButton = document.createElement("button");
|
||||||
saveSelectionButton.classList.add("button", "tool");
|
saveSelectionButton.classList.add("button", "tool");
|
||||||
saveSelectionButton.textContent = "Save";
|
saveSelectionButton.textContent = "Save Sel.";
|
||||||
saveSelectionButton.title = "Saves Selection";
|
saveSelectionButton.title = "Saves Selection";
|
||||||
saveSelectionButton.onclick = () => {
|
saveSelectionButton.onclick = () => {
|
||||||
downloadCanvas({
|
downloadCanvas({
|
||||||
|
@ -655,25 +701,72 @@ const selectTransformTool = () =>
|
||||||
actionArray.appendChild(saveSelectionButton);
|
actionArray.appendChild(saveSelectionButton);
|
||||||
actionArray.appendChild(createResourceButton);
|
actionArray.appendChild(createResourceButton);
|
||||||
|
|
||||||
|
// Some useful actions to do with selection
|
||||||
|
const visibleActionArray = document.createElement("div");
|
||||||
|
visibleActionArray.classList.add("button-array");
|
||||||
|
|
||||||
|
// Save Visible button
|
||||||
|
const saveVisibleSelectionButton = document.createElement("button");
|
||||||
|
saveVisibleSelectionButton.classList.add("button", "tool");
|
||||||
|
saveVisibleSelectionButton.textContent = "Save Vis.";
|
||||||
|
saveVisibleSelectionButton.title = "Saves Visible Selection";
|
||||||
|
saveVisibleSelectionButton.onclick = () => {
|
||||||
|
const canvas = uil.getVisible(state.selected, {
|
||||||
|
categories: ["image", "user", "select-display"],
|
||||||
|
});
|
||||||
|
downloadCanvas({
|
||||||
|
cropToContent: false,
|
||||||
|
canvas,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save Visible as Resource Button
|
||||||
|
const createVisibleResourceButton = document.createElement("button");
|
||||||
|
createVisibleResourceButton.classList.add("button", "tool");
|
||||||
|
createVisibleResourceButton.textContent = "Vis. to Res.";
|
||||||
|
createVisibleResourceButton.title =
|
||||||
|
"Saves Visible Selection as a Resource";
|
||||||
|
createVisibleResourceButton.onclick = () => {
|
||||||
|
const canvas = uil.getVisible(state.selected, {
|
||||||
|
categories: ["image", "user", "select-display"],
|
||||||
|
});
|
||||||
|
const image = document.createElement("img");
|
||||||
|
image.src = canvas.toDataURL();
|
||||||
|
image.onload = () => {
|
||||||
|
tools.stamp.state.addResource("Selection Resource", image);
|
||||||
|
tools.stamp.enable();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
visibleActionArray.appendChild(saveVisibleSelectionButton);
|
||||||
|
visibleActionArray.appendChild(createVisibleResourceButton);
|
||||||
|
|
||||||
// Disable buttons (if nothing is selected)
|
// Disable buttons (if nothing is selected)
|
||||||
state.ctxmenu.disableButtons = () => {
|
state.ctxmenu.disableButtons = () => {
|
||||||
saveSelectionButton.disabled = true;
|
saveSelectionButton.disabled = true;
|
||||||
createResourceButton.disabled = true;
|
createResourceButton.disabled = true;
|
||||||
|
saveVisibleSelectionButton.disabled = true;
|
||||||
|
createVisibleResourceButton.disabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Disable buttons (if something is selected)
|
// Disable buttons (if something is selected)
|
||||||
state.ctxmenu.enableButtons = () => {
|
state.ctxmenu.enableButtons = () => {
|
||||||
saveSelectionButton.disabled = "";
|
saveSelectionButton.disabled = "";
|
||||||
createResourceButton.disabled = "";
|
createResourceButton.disabled = "";
|
||||||
|
saveVisibleSelectionButton.disabled = "";
|
||||||
|
createVisibleResourceButton.disabled = "";
|
||||||
};
|
};
|
||||||
state.ctxmenu.actionArray = actionArray;
|
state.ctxmenu.actionArray = actionArray;
|
||||||
|
state.ctxmenu.visibleActionArray = visibleActionArray;
|
||||||
}
|
}
|
||||||
menu.appendChild(state.ctxmenu.snapToGridLabel);
|
menu.appendChild(state.ctxmenu.snapToGridLabel);
|
||||||
menu.appendChild(document.createElement("br"));
|
menu.appendChild(document.createElement("br"));
|
||||||
menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
|
menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
|
||||||
menu.appendChild(document.createElement("br"));
|
menu.appendChild(document.createElement("br"));
|
||||||
menu.appendChild(state.ctxmenu.useClipboardLabel);
|
menu.appendChild(state.ctxmenu.useClipboardLabel);
|
||||||
|
menu.appendChild(state.ctxmenu.selectionPeekOpacitySlider);
|
||||||
menu.appendChild(state.ctxmenu.actionArray);
|
menu.appendChild(state.ctxmenu.actionArray);
|
||||||
|
menu.appendChild(state.ctxmenu.visibleActionArray);
|
||||||
},
|
},
|
||||||
shortcut: "S",
|
shortcut: "S",
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,18 +151,23 @@ const stampTool = () =>
|
||||||
);
|
);
|
||||||
const resourceWrapper = document.createElement("div");
|
const resourceWrapper = document.createElement("div");
|
||||||
resourceWrapper.id = `resource-${resource.id}`;
|
resourceWrapper.id = `resource-${resource.id}`;
|
||||||
|
resourceWrapper.title = resource.name;
|
||||||
resourceWrapper.classList.add("resource", "list-item");
|
resourceWrapper.classList.add("resource", "list-item");
|
||||||
const resourceTitle = document.createElement("input");
|
const resourceTitle = document.createElement("input");
|
||||||
resourceTitle.value = resource.name;
|
resourceTitle.value = resource.name;
|
||||||
resourceTitle.title = resource.name;
|
|
||||||
resourceTitle.style.pointerEvents = "none";
|
resourceTitle.style.pointerEvents = "none";
|
||||||
resourceTitle.addEventListener("change", () => {
|
resourceTitle.addEventListener("change", () => {
|
||||||
resource.name = resourceTitle.value;
|
resource.name = resourceTitle.value;
|
||||||
resource.dirty = true;
|
resource.dirty = true;
|
||||||
resourceTitle.title = resourceTitle.value;
|
resourceWrapper.title = resourceTitle.value;
|
||||||
|
|
||||||
syncResources();
|
syncResources();
|
||||||
});
|
});
|
||||||
|
resourceTitle.addEventListener("keyup", function (event) {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
resourceTitle.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
resourceTitle.addEventListener("blur", () => {
|
resourceTitle.addEventListener("blur", () => {
|
||||||
resourceTitle.style.pointerEvents = "none";
|
resourceTitle.style.pointerEvents = "none";
|
||||||
|
@ -301,6 +306,7 @@ const stampTool = () =>
|
||||||
|
|
||||||
const vpc = viewport.canvasToView(x, y);
|
const vpc = viewport.canvasToView(x, y);
|
||||||
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
|
state.erasePrevCursor && state.erasePrevCursor();
|
||||||
|
|
||||||
uiCtx.save();
|
uiCtx.save();
|
||||||
|
|
||||||
|
@ -314,16 +320,7 @@ const stampTool = () =>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw current cursor location
|
// Draw current cursor location
|
||||||
uiCtx.lineWidth = 3;
|
state.erasePrevCursor = _tool._cursor_draw(x, y);
|
||||||
uiCtx.strokeStyle = "#FFF";
|
|
||||||
|
|
||||||
uiCtx.beginPath();
|
|
||||||
uiCtx.moveTo(vpc.x, vpc.y + 10);
|
|
||||||
uiCtx.lineTo(vpc.x, vpc.y - 10);
|
|
||||||
uiCtx.moveTo(vpc.x + 10, vpc.y);
|
|
||||||
uiCtx.lineTo(vpc.x - 10, vpc.y);
|
|
||||||
uiCtx.stroke();
|
|
||||||
|
|
||||||
uiCtx.restore();
|
uiCtx.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue