|
|
const FC_resizeBorder = 8; |
|
|
const FC_minimumSize = 32; |
|
|
|
|
|
|
|
|
|
|
|
function style2value(style) { |
|
|
try { |
|
|
const re = /calc\((-?\d+(?:\.\d+)?)px\)/; |
|
|
return parseFloat(style.match(re)[1]); |
|
|
} catch { |
|
|
return parseFloat(style); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function clamp01(v) { |
|
|
return Math.min(Math.max(v, 0.0), 1.0); |
|
|
} |
|
|
|
|
|
|
|
|
function clampMinMax(v, min, max) { |
|
|
return Math.min(Math.max(v, min), max); |
|
|
} |
|
|
|
|
|
|
|
|
class ForgeCoupleBox { |
|
|
|
|
|
|
|
|
constructor(image, field, mode) { |
|
|
const tab = gradioApp().getElementById((mode === "t2i") ? "tab_txt2img" : "tab_img2img"); |
|
|
|
|
|
|
|
|
this.img = image; |
|
|
|
|
|
|
|
|
this.box = document.createElement("div"); |
|
|
this.box.classList.add(`fc_bbox`); |
|
|
this.box.style.display = "none"; |
|
|
|
|
|
|
|
|
this.resize = {}; |
|
|
|
|
|
this.padding = {}; |
|
|
|
|
|
this.margin = {}; |
|
|
|
|
|
this.step = {}; |
|
|
|
|
|
|
|
|
this.cachedRow = null; |
|
|
|
|
|
this.registerClick(tab); |
|
|
this.registerHover(tab); |
|
|
this.registerUp(field, tab); |
|
|
|
|
|
image.parentElement.appendChild(this.box); |
|
|
} |
|
|
|
|
|
|
|
|
get imgBound() { |
|
|
return this.img.getBoundingClientRect(); |
|
|
} |
|
|
|
|
|
|
|
|
get boxBound() { |
|
|
return this.box.getBoundingClientRect(); |
|
|
} |
|
|
|
|
|
|
|
|
registerClick(tab) { |
|
|
this.img.addEventListener("mousedown", (e) => { |
|
|
if (e.button !== 0) |
|
|
return; |
|
|
|
|
|
this.isValid = (this.img.style.cursor != "default"); |
|
|
this.isResize = (this.resize.L || this.resize.R || this.resize.T || this.resize.B); |
|
|
|
|
|
if (this.isValid) { |
|
|
this.initCoord(); |
|
|
|
|
|
this.init = { |
|
|
X: e.clientX, |
|
|
Y: e.clientY, |
|
|
left: style2value(this.box.style.left), |
|
|
top: style2value(this.box.style.top) |
|
|
}; |
|
|
|
|
|
tab.style.cursor = this.img.style.cursor; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
registerHover(tab) { |
|
|
tab.addEventListener("mousemove", (e) => { |
|
|
|
|
|
if (!this.isValid) { |
|
|
this.checkMouse(e.clientX, e.clientY); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (this.isResize) |
|
|
this.resizeLogic(e.clientX, e.clientY) |
|
|
else |
|
|
this.offsetLogic(e.clientX, e.clientY) |
|
|
|
|
|
}); |
|
|
} |
|
|
|
|
|
registerUp(field, tab) { |
|
|
["mouseup", "mouseleave"].forEach((ev) => { |
|
|
tab.addEventListener(ev, (e) => { |
|
|
if (!this.isValid || (ev === "mouseup" && e.button !== 0)) |
|
|
return; |
|
|
|
|
|
field.value = this.styleToMapping(); |
|
|
updateInput(field); |
|
|
|
|
|
this.isValid = false; |
|
|
tab.style.cursor = "unset"; |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
resizeLogic(mouseX, mouseY) { |
|
|
if (this.resize.R) { |
|
|
const W = clampMinMax(mouseX - this.boxBound.left, FC_minimumSize, |
|
|
this.imgBound.right + this.padding.left - this.margin.left - this.init.left |
|
|
); |
|
|
|
|
|
this.box.style.width = `${this.step.w * Math.round(W / this.step.w)}px`; |
|
|
} else if (this.resize.L) { |
|
|
const rightEdge = style2value(this.box.style.left) + style2value(this.box.style.width); |
|
|
const W = clampMinMax(this.boxBound.right - mouseX, FC_minimumSize, rightEdge - this.padding.left) |
|
|
|
|
|
this.box.style.left = `${rightEdge - this.step.w * Math.round(W / this.step.w)}px`; |
|
|
this.box.style.width = `${this.step.w * Math.round(W / this.step.w)}px`; |
|
|
} |
|
|
|
|
|
if (this.resize.B) { |
|
|
const H = clampMinMax(mouseY - this.boxBound.top, FC_minimumSize, |
|
|
this.imgBound.bottom + this.padding.top - this.margin.top - this.init.top |
|
|
); |
|
|
|
|
|
this.box.style.height = `${this.step.h * Math.round(H / this.step.h)}px`; |
|
|
} else if (this.resize.T) { |
|
|
const bottomEdge = style2value(this.box.style.top) + style2value(this.box.style.height); |
|
|
const H = clampMinMax(this.boxBound.bottom - mouseY, FC_minimumSize, bottomEdge - this.padding.top); |
|
|
|
|
|
this.box.style.top = `${bottomEdge - this.step.h * Math.round(H / this.step.h)}px`; |
|
|
this.box.style.height = `${this.step.h * Math.round(H / this.step.h)}px`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
offsetLogic(mouseX, mouseY) { |
|
|
const deltaX = mouseX - this.init.X; |
|
|
const deltaY = mouseY - this.init.Y; |
|
|
|
|
|
const newLeft = clampMinMax(this.init.left + deltaX, |
|
|
this.padding.left, this.imgBound.width - this.boxBound.width + this.padding.left); |
|
|
|
|
|
const newTop = clampMinMax(this.init.top + deltaY, |
|
|
this.padding.top, this.imgBound.height - this.boxBound.height + this.padding.top); |
|
|
|
|
|
this.box.style.left = `${this.step.w * Math.round(newLeft / this.step.w)}px`; |
|
|
this.box.style.top = `${this.step.h * Math.round(newTop / this.step.h)}px`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
showBox(color, row) { |
|
|
this.cachedRow = row; |
|
|
|
|
|
setTimeout(() => { |
|
|
this.initCoord(); |
|
|
this.box.style.background = color; |
|
|
this.box.style.display = "block"; |
|
|
}, 25); |
|
|
} |
|
|
|
|
|
hideBox() { |
|
|
this.cachedRow = null; |
|
|
this.box.style.display = "none"; |
|
|
} |
|
|
|
|
|
initCoord() { |
|
|
if (this.cachedRow == null) |
|
|
return; |
|
|
|
|
|
const [from_x, delta_x, from_y, delta_y] = this.mappingToStyle(this.cachedRow); |
|
|
const { width, height } = this.imgBound; |
|
|
|
|
|
if (width === height) { |
|
|
this.padding.left = 0.0; |
|
|
this.padding.top = 0.0; |
|
|
} else if (width > height) { |
|
|
const ratio = height / width; |
|
|
this.padding.left = 0.0; |
|
|
this.padding.top = 256.0 * (1.0 - ratio); |
|
|
} else { |
|
|
const ratio = width / height; |
|
|
this.padding.left = 256.0 * (1.0 - ratio); |
|
|
this.padding.top = 0.0; |
|
|
} |
|
|
|
|
|
this.step.w = width / 100.0; |
|
|
this.step.h = height / 100.0; |
|
|
|
|
|
this.margin.left = this.imgBound.left; |
|
|
this.margin.top = this.imgBound.top; |
|
|
|
|
|
this.box.style.width = `${width * delta_x}px`; |
|
|
this.box.style.height = `${height * delta_y}px`; |
|
|
|
|
|
this.box.style.left = `${this.padding.left + width * from_x}px`; |
|
|
this.box.style.top = `${this.padding.top + height * from_y}px`; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
checkMouse(mouseX, mouseY) { |
|
|
if (this.box.style.display == "none") { |
|
|
this.img.style.cursor = "default"; |
|
|
return; |
|
|
} |
|
|
|
|
|
const { left, right, top, bottom } = this.boxBound; |
|
|
|
|
|
if (mouseX < left - FC_resizeBorder || mouseX > right + FC_resizeBorder || mouseY < top - FC_resizeBorder || mouseY > bottom + FC_resizeBorder) { |
|
|
this.img.style.cursor = "default"; |
|
|
return; |
|
|
} |
|
|
|
|
|
this.resize.L = mouseX < left + FC_resizeBorder; |
|
|
this.resize.R = mouseX > right - FC_resizeBorder; |
|
|
this.resize.T = mouseY < top + FC_resizeBorder; |
|
|
this.resize.B = mouseY > bottom - FC_resizeBorder; |
|
|
|
|
|
if (!(this.resize.L || this.resize.T || this.resize.R || this.resize.B)) { |
|
|
this.img.style.cursor = "move"; |
|
|
return; |
|
|
} |
|
|
|
|
|
if (this.resize.R && this.resize.B) |
|
|
this.img.style.cursor = "nwse-resize"; |
|
|
else if (this.resize.R && this.resize.T) |
|
|
this.img.style.cursor = "nesw-resize"; |
|
|
else if (this.resize.L && this.resize.B) |
|
|
this.img.style.cursor = "nesw-resize"; |
|
|
else if (this.resize.L && this.resize.T) |
|
|
this.img.style.cursor = "nwse-resize"; |
|
|
else if (this.resize.R || this.resize.L) |
|
|
this.img.style.cursor = "ew-resize"; |
|
|
else if (this.resize.B || this.resize.T) |
|
|
this.img.style.cursor = "ns-resize"; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mappingToStyle(row) { |
|
|
const x = row.querySelectorAll("span")[0].textContent; |
|
|
const y = row.querySelectorAll("span")[1].textContent; |
|
|
|
|
|
const [from_x, to_x] = x.split(":"); |
|
|
const [from_y, to_y] = y.split(":"); |
|
|
|
|
|
return [ |
|
|
parseFloat(from_x), |
|
|
parseFloat(to_x - from_x), |
|
|
parseFloat(from_y), |
|
|
parseFloat(to_y - from_y) |
|
|
] |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
styleToMapping() { |
|
|
const { width, height } = this.imgBound; |
|
|
const { left, right, top, bottom } = this.boxBound; |
|
|
const { left: leftMargin, top: topMargin } = this.margin; |
|
|
|
|
|
const from_x = (left - leftMargin) / width; |
|
|
const to_x = (right - leftMargin) / width; |
|
|
const from_y = (top - topMargin) / height; |
|
|
const to_y = (bottom - topMargin) / height; |
|
|
|
|
|
return `${clamp01(from_x)},${clamp01(to_x)},${clamp01(from_y)},${clamp01(to_y)}`; |
|
|
} |
|
|
|
|
|
} |
|
|
|