openOutpaint/js/ui/toolbar.js

301 lines
7.9 KiB
JavaScript
Raw Normal View History

/**
* Toolbar
*/
const toolbar = {
_toolbar: document.getElementById("ui-toolbar"),
tools: [],
_makeToolbarEntry: (tool) => {
const toolTitle = document.createElement("img");
toolTitle.classList.add("tool-icon");
toolTitle.src = tool.icon;
const toolEl = document.createElement("div");
toolEl.id = `tool-${tool.id}`;
toolEl.classList.add("tool");
toolEl.title = tool.name;
if (tool.options.shortcut) toolEl.title += ` (${tool.options.shortcut})`;
toolEl.onclick = () => tool.enable();
toolEl.appendChild(toolTitle);
return toolEl;
},
registerTool(
icon,
toolname,
enable,
disable,
options = {
/**
* Runs on tool creation. It receives the tool state.
*
* Can be used to setup callback functions, for example.
*/
init: null,
/**
* Function to populate the state menu.
*
* It receives a div element (that is the menu) and the current tool state.
*/
populateContextMenu: null,
/**
* Help description of the tool; for now used for nothing
*/
description: "",
/**
* Shortcut; Text describing this tool's shortcut access
*/
shortcut: "",
}
) {
// Set some defaults
if (!options.init)
options.init = (state) => console.debug(`Initialized tool '${toolname}'`);
if (!options.populateContextMenu)
options.populateContextMenu = (menu, state) => {
const span = document.createElement("span");
span.textContent = "Tool has no context menu";
menu.appendChild(span);
return;
};
// Create tool
const id = guid();
const contextMenuEl = document.getElementById("tool-context");
const tool = {
id,
icon,
name: toolname,
enabled: false,
_element: null,
state: {},
options,
enable: (opt = null) => {
this.tools.filter((t) => t.enabled).forEach((t) => t.disable());
while (contextMenuEl.lastChild) {
contextMenuEl.removeChild(contextMenuEl.lastChild);
}
options.populateContextMenu(contextMenuEl, tool.state);
tool._element && tool._element.classList.add("using");
tool.enabled = true;
enable(tool.state, opt);
},
disable: (opt = null) => {
tool._element && tool._element.classList.remove("using");
disable(tool.state, opt);
tool.enabled = false;
},
};
// Initalize
options.init && options.init(tool.state);
this.tools.push(tool);
// Add tool to toolbar
tool._element = this._makeToolbarEntry(tool);
this._toolbar.appendChild(tool._element);
return tool;
},
addSeparator() {
const separator = document.createElement("div");
separator.classList.add("separator");
this._toolbar.appendChild(separator);
},
};
/**
* Dream and img2img tools
*/
const _reticle_draw = (evn, snapToGrid = true) => {
if (evn.target.id === "overlayCanvas") {
const bb = getBoundingBox(
evn.x,
evn.y,
basePixelCount * scaleFactor,
basePixelCount * scaleFactor,
snapToGrid && basePixelCount
);
// draw targeting square reticle thingy cursor
ovCtx.strokeStyle = "#FFF";
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame
}
};
const tools = {};
/**
* Dream tool
*/
tools.dream = toolbar.registerTool(
"res/icons/image-plus.svg",
"Dream",
(state, opt) => {
// Draw new cursor immediately
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
_reticle_draw({...mouse.canvas.pos, target: {id: "overlayCanvas"}});
// Start Listeners
mouse.listen.canvas.onmousemove.on(state.mousemovecb);
mouse.listen.canvas.left.onclick.on(state.dreamcb);
mouse.listen.canvas.right.onclick.on(state.erasecb);
},
(state, opt) => {
// Clear Listeners
mouse.listen.canvas.onmousemove.clear(state.mousemovecb);
mouse.listen.canvas.left.onclick.clear(state.dreamcb);
mouse.listen.canvas.right.onclick.clear(state.erasecb);
},
{
init: (state) => {
state.snapToGrid = true;
state.mousemovecb = (evn) => _reticle_draw(evn, state.snapToGrid);
state.dreamcb = (evn) => {
dream_generate_callback(evn, state);
};
state.erasecb = (evn) => dream_erase_callback(evn, state);
},
populateContextMenu: (menu, state) => {
if (!state.ctxmenu) {
state.ctxmenu = {};
// Snap To Grid Checkbox
const snapToGridCheckbox = document.createElement("input");
snapToGridCheckbox.type = "checkbox";
snapToGridCheckbox.checked = state.snapToGrid;
snapToGridCheckbox.onchange = () =>
(state.snapToGrid = snapToGridCheckbox.checked);
state.ctxmenu.snapToGridCheckbox = snapToGridCheckbox;
const snapToGridLabel = document.createElement("label");
snapToGridLabel.appendChild(snapToGridCheckbox);
snapToGridLabel.appendChild(new Text("Snap to Grid"));
state.ctxmenu.snapToGridLabel = snapToGridLabel;
}
menu.appendChild(state.ctxmenu.snapToGridLabel);
},
shortcut: "D",
}
);
/**
* Mask Editing tools
*/
toolbar.addSeparator();
/**
* Mask Brush tool
*/
tools.maskbrush = toolbar.registerTool(
"res/icons/paintbrush.svg",
"Mask Brush",
(state, opt) => {
// Draw new cursor immediately
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
state.movecb({...mouse.canvas.pos, target: {id: "overlayCanvas"}});
// Start Listeners
mouse.listen.canvas.onmousemove.on(state.movecb);
mouse.listen.canvas.onwheel.on(state.wheelcb);
mouse.listen.canvas.left.onpaint.on(state.drawcb);
mouse.listen.canvas.right.onpaint.on(state.erasecb);
},
(state, opt) => {
// Clear Listeners
mouse.listen.canvas.onmousemove.clear(state.movecb);
mouse.listen.canvas.onwheel.on(state.wheelcb);
mouse.listen.canvas.left.onpaint.clear(state.drawcb);
mouse.listen.canvas.right.onpaint.clear(state.erasecb);
},
{
init: (state) => {
state.config = {
brushScrollSpeed: 1 / 4,
minBrushSize: 10,
maxBrushSize: 500,
};
state.brushSize = 64;
state.setBrushSize = (size) => {
state.brushSize = size;
state.ctxmenu.brushSizeRange.value = size;
state.ctxmenu.brushSizeText.value = size;
};
state.movecb = (evn) => {
if (evn.target.id === "overlayCanvas") {
// draw big translucent red blob cursor
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.fillStyle = "#FF6A6A50";
ovCtx.fill();
}
};
state.wheelcb = (evn) => {
if (evn.target.id === "overlayCanvas") {
state.setBrushSize(
Math.max(
state.config.minBrushSize,
Math.min(
state.config.maxBrushSize,
state.brushSize -
Math.floor(state.config.brushScrollSpeed * evn.delta)
)
)
);
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
state.movecb(evn);
}
};
state.drawcb = (evn) => mask_brush_draw_callback(evn, state);
state.erasecb = (evn) => mask_brush_erase_callback(evn, state);
},
populateContextMenu: (menu, state) => {
if (!state.ctxmenu) {
state.ctxmenu = {};
// Brush Size slider
const brushSizeRange = document.createElement("input");
brushSizeRange.type = "range";
brushSizeRange.value = state.brushSize;
brushSizeRange.max = state.config.maxBrushSize;
brushSizeRange.step = 8;
brushSizeRange.min = state.config.minBrushSize;
brushSizeRange.oninput = () =>
(state.brushSize = parseInt(brushSizeRange.value));
state.ctxmenu.brushSizeRange = brushSizeRange;
const brushSizeText = document.createElement("input");
brushSizeText.type = "number";
brushSizeText.value = state.brushSize;
brushSizeText.oninput = () =>
(state.brushSize = parseInt(brushSizeText.value));
state.ctxmenu.brushSizeText = brushSizeText;
const brushSizeLabel = document.createElement("label");
brushSizeLabel.appendChild(new Text("Brush Size"));
brushSizeLabel.appendChild(brushSizeText);
brushSizeLabel.appendChild(brushSizeRange);
state.ctxmenu.brushSizeLabel = brushSizeLabel;
}
menu.appendChild(state.ctxmenu.brushSizeLabel);
},
shortcut: "M",
}
);
toolbar.tools[0].enable();