allow shortcut deletion via callback

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>

Former-commit-id: 0a46ffda9945ad95665634c21c9732e7d9d1497e
This commit is contained in:
Victor Seiji Hariki 2022-11-24 12:30:13 -03:00
parent 2fdcdc7f0c
commit 42144ea577
10 changed files with 458 additions and 267 deletions

View file

@ -295,6 +295,7 @@ people, person, humans, human, divers, diver, glitch, error, text, watermark, ba
<!-- Load Tools -->
<script src="js/ui/tool/dream.js" type="text/javascript"></script>
<script src="js/ui/tool/maskbrush.js" type="text/javascript"></script>
<script src="js/ui/tool/select.js" type="text/javascript"></script>
<script src="js/ui/toolbar.js" type="text/javascript"></script>
</body>

View file

@ -201,6 +201,8 @@ window.onmouseup = (evn) => {
target: evn.target,
initialTarget: mouse.coords[name].dragging[key].target,
buttonId: evn.button,
ix: mouse.coords[name].dragging[key].x,
iy: mouse.coords[name].dragging[key].y,
x: mouse.coords[name].pos.x,
y: mouse.coords[name].pos.y,
evn,
@ -213,6 +215,8 @@ window.onmouseup = (evn) => {
target: evn.target,
initialTarget: mouse.coords[name].dragging[key].target,
buttonId: evn.button,
ix: mouse.coords[name].dragging[key].x,
iy: mouse.coords[name].dragging[key].y,
x: mouse.coords[name].pos.x,
y: mouse.coords[name].pos.y,
evn,
@ -264,6 +268,8 @@ window.onmousemove = (evn) => {
mouse.listen[name][key].ondragstart.emit({
target: evn.target,
buttonId: evn.button,
ix: mouse.coords[name].dragging[key].x,
iy: mouse.coords[name].dragging[key].y,
x: mouse.coords[name].pos.x,
y: mouse.coords[name].pos.y,
evn,
@ -283,6 +289,8 @@ window.onmousemove = (evn) => {
target: evn.target,
initialTarget: mouse.coords[name].dragging[key].target,
button: index,
ix: mouse.coords[name].dragging[key].x,
iy: mouse.coords[name].dragging[key].y,
px: mouse.coords[name].prev.x,
py: mouse.coords[name].prev.y,
x: mouse.coords[name].pos.x,
@ -297,6 +305,8 @@ window.onmousemove = (evn) => {
target: evn.target,
initialTarget: mouse.coords[name].dragging[key].target,
button: index,
ix: mouse.coords[name].dragging[key].x,
iy: mouse.coords[name].dragging[key].y,
px: mouse.coords[name].prev.x,
py: mouse.coords[name].prev.y,
x: mouse.coords[name].pos.x,
@ -384,7 +394,9 @@ const keyboard = {
},
deleteShortcut(id) {
this.shortcuts.keys().forEach((key) => {
this.shortcuts[key] = this.shortcuts[key].filter((v) => v.id !== id);
this.shortcuts[key] = this.shortcuts[key].filter(
(v) => v.id !== id && v.callback !== id
);
});
},

View file

@ -107,6 +107,7 @@ function createSlider(name, wrapper, options = {}) {
textEl.value = `${name}: ${value}`;
});
textEl.addEventListener("focus", () => {
overEl.style.pointerEvents = "none";
textEl.value = value;
});
@ -125,7 +126,6 @@ function createSlider(name, wrapper, options = {}) {
mouse.listen.window.left.onclick.on((evn) => {
if (evn.target === overEl) {
overEl.style.pointerEvents = "none";
textEl.select();
}
});

View file

@ -212,3 +212,179 @@ const dream_img2img_callback = (evn, state) => {
dream(bb.x, bb.y, request, {method: "img2img", stopMarching, bb});
}
};
/**
* Registers Tools
*/
const dreamTool = () =>
toolbar.registerTool(
"res/icons/image-plus.svg",
"Dream",
(state, opt) => {
// Draw new cursor immediately
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
state.mousemovecb({
...mouse.coords.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.overMaskPx = 0;
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 = {};
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
"Snap To Grid"
).label;
state.ctxmenu.overMaskPxLabel = _toolbar_input.slider(
state,
"overMaskPx",
"Overmask px",
0,
128,
1
).slider;
}
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.overMaskPxLabel);
},
shortcut: "D",
}
);
const img2imgTool = () =>
toolbar.registerTool(
"res/icons/image.svg",
"Img2Img",
(state, opt) => {
// Draw new cursor immediately
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
state.mousemovecb({
...mouse.coords.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.denoisingStrength = 0.7;
state.borderMaskSize = 64;
state.mousemovecb = (evn) => {
_reticle_draw(evn, state.snapToGrid);
const bb = getBoundingBox(
evn.x,
evn.y,
basePixelCount * scaleFactor,
basePixelCount * scaleFactor,
state.snapToGrid && basePixelCount
);
// For displaying border mask
const auxCanvas = document.createElement("canvas");
auxCanvas.width = bb.w;
auxCanvas.height = bb.h;
const auxCtx = auxCanvas.getContext("2d");
if (state.borderMaskSize > 0) {
auxCtx.fillStyle = "#FF6A6A50";
auxCtx.fillRect(0, 0, state.borderMaskSize, bb.h);
auxCtx.fillRect(0, 0, bb.w, state.borderMaskSize);
auxCtx.fillRect(
bb.w - state.borderMaskSize,
0,
state.borderMaskSize,
bb.h
);
auxCtx.fillRect(
0,
bb.h - state.borderMaskSize,
bb.w,
state.borderMaskSize
);
}
const tmp = ovCtx.globalAlpha;
ovCtx.globalAlpha = 0.4;
ovCtx.drawImage(auxCanvas, bb.x, bb.y);
ovCtx.globalAlpha = tmp;
};
state.dreamcb = (evn) => {
dream_img2img_callback(evn, state);
};
state.erasecb = (evn) => dream_erase_callback(evn, state);
},
populateContextMenu: (menu, state) => {
if (!state.ctxmenu) {
state.ctxmenu = {};
// Snap To Grid Checkbox
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
"Snap To Grid"
).label;
// Denoising Strength Slider
state.ctxmenu.denoisingStrengthSlider = _toolbar_input.slider(
state,
"denoisingStrength",
"Denoising Strength",
0,
1,
0.05
).slider;
// Border Mask Size Slider
state.ctxmenu.borderMaskSlider = _toolbar_input.slider(
state,
"borderMaskSize",
"Border Mask Size",
0,
128,
1
).slider;
}
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.denoisingStrengthSlider);
menu.appendChild(state.ctxmenu.borderMaskSlider);
},
shortcut: "I",
}
);

View file

@ -1,4 +1,4 @@
const mask_brush_draw_callback = (evn, state) => {
const _mask_brush_draw_callback = (evn, state) => {
if (evn.initialTarget.id === "overlayCanvas") {
maskPaintCtx.globalCompositeOperation = "source-over";
maskPaintCtx.strokeStyle = "#FF6A6A";
@ -12,7 +12,7 @@ const mask_brush_draw_callback = (evn, state) => {
}
};
const mask_brush_erase_callback = (evn, state) => {
const _mask_brush_erase_callback = (evn, state) => {
if (evn.initialTarget.id === "overlayCanvas") {
maskPaintCtx.globalCompositeOperation = "destination-out";
maskPaintCtx.strokeStyle = "#FFFFFFFF";
@ -25,3 +25,85 @@ const mask_brush_erase_callback = (evn, state) => {
maskPaintCtx.stroke();
}
};
const maskBrushTool = () =>
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.coords.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.brushSize = state.setBrushSize(
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 = {};
const brushSizeSlider = _toolbar_input.slider(
state,
"brushSize",
"Brush Size",
state.config.minBrushSize,
state.config.maxBrushSize,
1
);
state.ctxmenu.brushSizeSlider = brushSizeSlider.slider;
state.setBrushSize = brushSizeSlider.setValue;
}
menu.appendChild(state.ctxmenu.brushSizeSlider);
},
shortcut: "M",
}
);

0
js/ui/tool/populate.js Normal file
View file

153
js/ui/tool/select.js Normal file
View file

@ -0,0 +1,153 @@
const selectTransformTool = () =>
toolbar.registerTool(
"res/icons/box-select.svg",
"Select Image",
(state, opt) => {
// Draw new cursor immediately
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
mouse.listen.canvas.onmousemove.on(state.movecb);
mouse.listen.canvas.left.ondragstart.on(state.dragstartcb);
mouse.listen.canvas.left.ondragend.on(state.dragendcb);
mouse.listen.canvas.right.onclick.on(state.cancelcb);
},
(state, opt) => {
mouse.listen.canvas.onmousemove.clear(state.movecb);
mouse.listen.canvas.left.ondragstart.clear(state.dragstartcb);
mouse.listen.canvas.left.ondragend.clear(state.dragendcb);
mouse.listen.canvas.right.onclick.clear(state.cancelcb);
},
{
init: (state) => {
state.snapToGrid = true;
state.dragging = null;
const selectionBB = (x1, y1, x2, y2) => {
return {
x: Math.min(x1, x2),
y: Math.min(y1, y2),
w: Math.abs(x1 - x2),
h: Math.abs(y1 - y2),
};
};
state.movecb = (evn) => {
if (evn.target.id === "overlayCanvas") {
let x = evn.x;
let y = evn.y;
if (state.snapToGrid) {
x += snap(evn.x, true, 64);
y += snap(evn.y, true, 64);
}
// Draw dragging box
if (state.dragging) {
ovCtx.setLineDash([2, 2]);
ovCtx.lineWidth = 1;
ovCtx.strokeStyle = "#FFF";
const ix = state.dragging.ix;
const iy = state.dragging.iy;
const bb = selectionBB(ix, iy, x, y);
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h);
ovCtx.setLineDash([]);
}
// Draw selection box
if (state.selected) {
ovCtx.lineWidth = 1;
ovCtx.strokeStyle = "#FFF";
ovCtx.setLineDash([4, 2]);
ovCtx.strokeRect(
state.selected.x,
state.selected.y,
state.selected.w,
state.selected.h
);
ovCtx.setLineDash([]);
}
// Draw cuttent cursor location
ovCtx.lineWidth = 3;
ovCtx.strokeStyle = "#FFF";
ovCtx.beginPath();
ovCtx.moveTo(x, y + 10);
ovCtx.lineTo(x, y - 10);
ovCtx.moveTo(x + 10, y);
ovCtx.lineTo(x - 10, y);
ovCtx.stroke();
}
};
state.dragstartcb = (evn) => {
if (evn.target.id === "overlayCanvas") {
let ix = evn.ix;
let iy = evn.iy;
if (state.snapToGrid) {
ix += snap(evn.ix, true, 64);
iy += snap(evn.iy, true, 64);
}
state.dragging = {ix, iy};
}
};
state.dragendcb = (evn) => {
if (evn.target.id === "overlayCanvas" && state.dragging) {
let x = evn.x;
let y = evn.y;
if (state.snapToGrid) {
x += snap(evn.x, true, 64);
y += snap(evn.y, true, 64);
}
state.selected = selectionBB(
state.dragging.ix,
state.dragging.iy,
x,
y
);
state.dragging = null;
}
};
state.cancelcb = (evn) => {
if (evn.target.id === "overlayCanvas") {
if (state.dragging) state.dragging = null;
else state.selected = null;
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
state.movecb(evn);
}
};
// Keyboard callbacks
state.keydowncb = (evn) => {
console.debug(evn);
};
state.keyclickcb = (evn) => {
console.debug(evn);
};
},
populateContextMenu: (menu, state) => {
if (!state.ctxmenu) {
state.ctxmenu = {};
// Snap To Grid Checkbox
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
"Snap To Grid"
).label;
}
menu.appendChild(state.ctxmenu.snapToGridLabel);
},
}
);

View file

@ -134,15 +134,7 @@ const _toolbar_input = {
return {checkbox, label};
},
slider: (
state,
dataKey,
text,
min = 0,
max = 1,
step = 0.1,
defaultValue = 0.3
) => {
slider: (state, dataKey, text, min = 0, max = 1, step = 0.1) => {
const slider = document.createElement("div");
const value = createSlider(text, slider, {
@ -152,7 +144,7 @@ const _toolbar_input = {
valuecb: (v) => {
state[dataKey] = v;
},
defaultValue,
defaultValue: state[dataKey],
});
return {
@ -179,6 +171,7 @@ const _reticle_draw = (evn, snapToGrid = true) => {
);
// draw targeting square reticle thingy cursor
ovCtx.lineWidth = 1;
ovCtx.strokeStyle = "#FFF";
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame
}
@ -189,179 +182,8 @@ 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);
state.mousemovecb({
...mouse.coords.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.overMaskPx = 0;
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 = {};
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
"Snap To Grid"
).label;
state.ctxmenu.overMaskPxLabel = _toolbar_input.slider(
state,
"overMaskPx",
"Overmask px",
0,
128,
1,
64
).slider;
}
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.overMaskPxLabel);
},
shortcut: "D",
}
);
tools.img2img = toolbar.registerTool(
"res/icons/image.svg",
"Img2Img",
(state, opt) => {
// Draw new cursor immediately
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
state.mousemovecb({
...mouse.coords.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.denoisingStrength = 0.7;
state.borderMaskSize = 64;
state.mousemovecb = (evn) => {
_reticle_draw(evn, state.snapToGrid);
const bb = getBoundingBox(
evn.x,
evn.y,
basePixelCount * scaleFactor,
basePixelCount * scaleFactor,
state.snapToGrid && basePixelCount
);
// For displaying border mask
const auxCanvas = document.createElement("canvas");
auxCanvas.width = bb.w;
auxCanvas.height = bb.h;
const auxCtx = auxCanvas.getContext("2d");
if (state.borderMaskSize > 0) {
auxCtx.fillStyle = "#FF6A6A50";
auxCtx.fillRect(0, 0, state.borderMaskSize, bb.h);
auxCtx.fillRect(0, 0, bb.w, state.borderMaskSize);
auxCtx.fillRect(
bb.w - state.borderMaskSize,
0,
state.borderMaskSize,
bb.h
);
auxCtx.fillRect(
0,
bb.h - state.borderMaskSize,
bb.w,
state.borderMaskSize
);
}
const tmp = ovCtx.globalAlpha;
ovCtx.globalAlpha = 0.4;
ovCtx.drawImage(auxCanvas, bb.x, bb.y);
ovCtx.globalAlpha = tmp;
};
state.dreamcb = (evn) => {
dream_img2img_callback(evn, state);
};
state.erasecb = (evn) => dream_erase_callback(evn, state);
},
populateContextMenu: (menu, state) => {
if (!state.ctxmenu) {
state.ctxmenu = {};
// Snap To Grid Checkbox
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
"Snap To Grid"
).label;
// Denoising Strength Slider
state.ctxmenu.denoisingStrengthSlider = _toolbar_input.slider(
state,
"denoisingStrength",
"Denoising Strength",
0,
1,
0.05,
0.7
).slider;
// Border Mask Size Slider
state.ctxmenu.borderMaskSlider = _toolbar_input.slider(
state,
"borderMaskSize",
"Border Mask Size",
0,
128,
1,
64
).slider;
}
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.denoisingStrengthSlider);
menu.appendChild(state.ctxmenu.borderMaskSlider);
},
shortcut: "I",
}
);
tools.dream = dreamTool();
tools.img2img = img2imgTool();
/**
* Mask Editing tools
@ -371,86 +193,13 @@ 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.coords.canvas.pos, target: {id: "overlayCanvas"}});
tools.maskbrush = maskBrushTool();
// 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,
};
/**
* Image Editing tools
*/
toolbar.addSeparator();
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.brushSize = state.setBrushSize(
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 = {};
const brushSizeSlider = _toolbar_input.slider(
state,
"brushSize",
"Brush Size",
state.config.minBrushSize,
state.config.maxBrushSize,
1,
64
);
state.ctxmenu.brushSizeSlider = brushSizeSlider.slider;
state.setBrushSize = brushSizeSlider.setValue;
}
menu.appendChild(state.ctxmenu.brushSizeSlider);
},
shortcut: "M",
}
);
tools.selecttransform = selectTransformTool();
toolbar.tools[0].enable();

View file

@ -64,7 +64,10 @@ function snap(i, scaled = true, gridSize = 64) {
scaleOffset = gridSize / 2;
}
}
var snapOffset = (i % gridSize) - scaleOffset;
const modulus = i % gridSize;
var snapOffset = modulus - scaleOffset;
if (modulus > gridSize / 2) snapOffset = modulus - gridSize;
if (snapOffset == 0) {
return snapOffset;
}

15
res/icons/box-select.svg Normal file
View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 3a2 2 0 0 0-2 2"></path>
<path d="M19 3a2 2 0 0 1 2 2"></path>
<path d="M21 19a2 2 0 0 1-2 2"></path>
<path d="M5 21a2 2 0 0 1-2-2"></path>
<path d="M9 3h1"></path>
<path d="M9 21h1"></path>
<path d="M14 3h1"></path>
<path d="M14 21h1"></path>
<path d="M3 9v1"></path>
<path d="M21 9v1"></path>
<path d="M3 14v1"></path>
<path d="M21 14v1"></path>
</svg>

After

Width:  |  Height:  |  Size: 573 B