Merge pull request #295 from Metachs/select-overhaul

Select tool overhaul
This commit is contained in:
tim h 2024-08-31 10:38:13 -05:00 committed by GitHub
commit bf9e6d0b80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 395 additions and 117 deletions

View file

@ -202,3 +202,9 @@
-webkit-mask-image: url("../res/icons/squircle.svg"); -webkit-mask-image: url("../res/icons/squircle.svg");
mask-image: url("../res/icons/squircle.svg"); mask-image: url("../res/icons/squircle.svg");
} }
.ui.inline-icon.icon-file-plus::after,
.ui.icon > .icon-file-plus {
-webkit-mask-image: url("../res/icons/file-plus.svg");
mask-image: url("../res/icons/file-plus.svg");
}

View file

@ -339,7 +339,7 @@
<br /> <br />
<span id="version"> <span id="version">
<a href="https://github.com/zero01101/openOutpaint" target="_blank"> <a href="https://github.com/zero01101/openOutpaint" target="_blank">
v20240427.001 v20240824.001
</a> </a>
<br /> <br />
<a <a
@ -545,7 +545,7 @@
type="text/javascript"></script> type="text/javascript"></script>
<script src="js/lib/events.js?v=2ab7933" type="text/javascript"></script> <script src="js/lib/events.js?v=2ab7933" type="text/javascript"></script>
<script src="js/lib/db.js?v=434363b" type="text/javascript"></script> <script src="js/lib/db.js?v=434363b" type="text/javascript"></script>
<script src="js/lib/input.js?v=769485c" type="text/javascript"></script> <script src="js/lib/input.js?v=f99493d" type="text/javascript"></script>
<script src="js/lib/layers.js?v=26b7436" type="text/javascript"></script> <script src="js/lib/layers.js?v=26b7436" type="text/javascript"></script>
<script src="js/lib/commands.js?v=ad60afc" type="text/javascript"></script> <script src="js/lib/commands.js?v=ad60afc" type="text/javascript"></script>
@ -576,7 +576,7 @@
src="js/ui/tool/generic.js?v=3e678e0" src="js/ui/tool/generic.js?v=3e678e0"
type="text/javascript"></script> type="text/javascript"></script>
<script src="js/ui/tool/dream.js?v=56c7c50" type="text/javascript"></script> <script src="js/ui/tool/dream.js?v=9ae0612" type="text/javascript"></script>
<script <script
src="js/ui/tool/maskbrush.js?v=e9bd0eb" src="js/ui/tool/maskbrush.js?v=e9bd0eb"
type="text/javascript"></script> type="text/javascript"></script>

View file

@ -1534,7 +1534,7 @@ async function upscaleAndDownload(
body: JSON.stringify(data), body: JSON.stringify(data),
}) })
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then(async (data) => {
console.log(data); console.log(data);
var link = document.createElement("a"); var link = document.createElement("a");
link.download = link.download =
@ -1554,6 +1554,7 @@ async function upscaleAndDownload(
console.log("Add upscaled to resource"); console.log("Add upscaled to resource");
const img = new Image(); const img = new Image();
img.src = link.href; img.src = link.href;
await img.decode();
tools.stamp.state.addResource(guid() + " (upscaled)", img); tools.stamp.state.addResource(guid() + " (upscaled)", img);
} }
if (download == true) { if (download == true) {

View file

@ -21,6 +21,14 @@ const commands = makeReadOnly(
_history: [], _history: [],
/** The types of commands we can run (private) */ /** The types of commands we can run (private) */
_types: {}, _types: {},
/** @type {Observer<{n: int, cancel: function}>} */
get onundo() { return this._onundo; },
_onundo: new Observer(),
/** @type {Observer<{n: int, cancel: function}>} */
get onredo() { return this._onredo; },
_onredo: new Observer(),
/** /**
* Undoes the last commands in the history * Undoes the last commands in the history
@ -28,6 +36,12 @@ const commands = makeReadOnly(
* @param {number} [n] Number of actions to undo * @param {number} [n] Number of actions to undo
*/ */
async undo(n = 1) { async undo(n = 1) {
var cancelled = false;
await this._onundo.emit({
n:n,
cancel: ()=>{cancelled=true;},
});
if (cancelled) return;
for (var i = 0; i < n && this.current > -1; i++) { for (var i = 0; i < n && this.current > -1; i++) {
try { try {
await this._history[this._current--].undo(); await this._history[this._current--].undo();
@ -45,6 +59,12 @@ const commands = makeReadOnly(
* @param {number} [n] Number of actions to redo * @param {number} [n] Number of actions to redo
*/ */
async redo(n = 1) { async redo(n = 1) {
let cancelled = false;
await this._onredo.emit({
n:n,
cancel: ()=>{cancelled=true;},
});
if (cancelled) return;
for (var i = 0; i < n && this.current + 1 < this._history.length; i++) { for (var i = 0; i < n && this.current + 1 < this._history.length; i++) {
try { try {
await this._history[++this._current].redo(); await this._history[++this._current].redo();

View file

@ -660,6 +660,9 @@ window.onkeyup = (evn) => {
}); });
} }
if (keyboard.keys[evn.code]._hold_to)
clearTimeout(keyboard.keys[evn.code]._hold_to);
keyboard.keys[evn.code] = { keyboard.keys[evn.code] = {
pressed: false, pressed: false,
held: false, held: false,

View file

@ -3339,6 +3339,7 @@ function addControlNetToAlwaysOnScripts(state, initCanvas, maskCanvas) {
//img2img? //img2img?
state.alwayson_scripts.controlnet.args = [ state.alwayson_scripts.controlnet.args = [
{ {
enabled: true,
module: extensions.selectedControlNetModule, module: extensions.selectedControlNetModule,
model: extensions.selectedControlNetModel, model: extensions.selectedControlNetModel,
control_mode: document.getElementById("controlNetMode-select").value, control_mode: document.getElementById("controlNetMode-select").value,
@ -3351,10 +3352,11 @@ function addControlNetToAlwaysOnScripts(state, initCanvas, maskCanvas) {
} else { } else {
state.alwayson_scripts.controlnet.args = [ state.alwayson_scripts.controlnet.args = [
{ {
enabled: true,
module: extensions.selectedControlNetModule, module: extensions.selectedControlNetModule,
model: extensions.selectedControlNetModel, model: extensions.selectedControlNetModel,
control_mode: document.getElementById("controlNetMode-select").value, control_mode: document.getElementById("controlNetMode-select").value,
input_image: initimg, //initCanvas.toDataURL(), image: initimg, //initCanvas.toDataURL(),
mask: maskimg, //maskCanvas.toDataURL(), mask: maskimg, //maskCanvas.toDataURL(),
processor_res: 64, processor_res: 64,
resize_mode: document.getElementById("controlNetResize-select").value, resize_mode: document.getElementById("controlNetResize-select").value,

View file

@ -13,6 +13,9 @@ const selectTransformTool = () =>
mouse.listen.world.btn.left.ondragstart.on(state.dragstartcb); mouse.listen.world.btn.left.ondragstart.on(state.dragstartcb);
mouse.listen.world.btn.left.ondrag.on(state.dragcb); mouse.listen.world.btn.left.ondrag.on(state.dragcb);
mouse.listen.world.btn.left.ondragend.on(state.dragendcb); mouse.listen.world.btn.left.ondragend.on(state.dragendcb);
mouse.listen.world.btn.left.ondclick.on(state.dclickcb);
mouse.listen.world.btn.right.ondclick.on(state.drclickcb);
// Canvas right mouse handler // Canvas right mouse handler
mouse.listen.world.btn.right.onclick.on(state.cancelcb); mouse.listen.world.btn.right.onclick.on(state.cancelcb);
@ -23,6 +26,10 @@ const selectTransformTool = () =>
// Layer system handlers // Layer system handlers
uil.onactive.on(state.uilayeractivecb); uil.onactive.on(state.uilayeractivecb);
// Undo
commands.onundo.on(state.undocb);
commands.onredo.on(state.redocb);
// Registers keyboard shortcuts // Registers keyboard shortcuts
keyboard.onShortcut({ctrl: true, key: "KeyA"}, state.ctrlacb); keyboard.onShortcut({ctrl: true, key: "KeyA"}, state.ctrlacb);
@ -34,6 +41,15 @@ const selectTransformTool = () =>
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb); keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb); keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb);
keyboard.onShortcut({key: "Equal"}, state.togglemirror); keyboard.onShortcut({key: "Equal"}, state.togglemirror);
keyboard.onShortcut({key: "Enter"}, state.entercb);
keyboard.onShortcut({shift: true, key: "Enter"}, state.sentercb);
keyboard.onShortcut({ctrl: true, key: "Enter"}, state.ctentercb);
keyboard.onShortcut({ctrl: true, shift: true, key: "Enter"}, state.sctentercb);
keyboard.onShortcut({key: "Delete"}, state.delcb);
keyboard.onShortcut({shift: true, key: "Delete"}, state.sdelcb);
keyboard.onShortcut({key: "Escape"}, state.escapecb);
state.ctxmenu.mirrorSelectionCheckbox.disabled = true; state.ctxmenu.mirrorSelectionCheckbox.disabled = true;
state.selected = null; state.selected = null;
@ -51,20 +67,34 @@ const selectTransformTool = () =>
mouse.listen.world.btn.left.ondragstart.clear(state.dragstartcb); mouse.listen.world.btn.left.ondragstart.clear(state.dragstartcb);
mouse.listen.world.btn.left.ondrag.clear(state.dragcb); mouse.listen.world.btn.left.ondrag.clear(state.dragcb);
mouse.listen.world.btn.left.ondragend.clear(state.dragendcb); mouse.listen.world.btn.left.ondragend.clear(state.dragendcb);
mouse.listen.world.btn.left.ondclick.clear(state.dclickcb);
mouse.listen.world.btn.right.ondclick.clear(state.drclickcb);
mouse.listen.world.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);
keyboard.deleteShortcut(state.ctrlacb, "KeyA"); keyboard.deleteShortcut(state.ctrlacb, "KeyA");
keyboard.deleteShortcut(state.ctrlsacb, "KeyA"); keyboard.deleteShortcut(state.ctrlsacb, "KeyA");
keyboard.deleteShortcut(state.ctrlccb, "KeyC"); keyboard.deleteShortcut(state.ctrlccb, "KeyC");
keyboard.deleteShortcut(state.ctrlvcb, "KeyV"); keyboard.deleteShortcut(state.ctrlvcb, "KeyV");
keyboard.deleteShortcut(state.ctrlxcb, "KeyX"); keyboard.deleteShortcut(state.ctrlxcb, "KeyX");
keyboard.deleteShortcut(state.togglemirror, "Equal"); keyboard.deleteShortcut(state.togglemirror, "Equal");
keyboard.deleteShortcut(state.entercb,"Enter");
keyboard.deleteShortcut(state.sentercb,"Enter");
keyboard.deleteShortcut(state.ctentercb,"Enter");
keyboard.deleteShortcut(state.sctentercb,"Enter");
keyboard.deleteShortcut(state.delcb,"Delete");
keyboard.deleteShortcut(state.sdelcb,"Delete");
keyboard.deleteShortcut(state.escapecb,"Escape");
uil.onactive.clear(state.uilayeractivecb); uil.onactive.clear(state.uilayeractivecb);
commands.onundo.clear(state.undocb);
commands.onredo.clear(state.redocb);
// Clear any selections // Clear any selections
state.reset(); state.reset();
@ -85,6 +115,9 @@ const selectTransformTool = () =>
state.snapToGrid = true; state.snapToGrid = true;
state.keepAspectRatio = true; state.keepAspectRatio = true;
state.block_res_change = true; state.block_res_change = true;
state.toNewLayer = false;
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
@ -179,6 +212,19 @@ const selectTransformTool = () =>
); );
} }
}; };
// Undo/Redo Handling, reset state before Undo/Redo
state.undocb= (undo)=>{
if (state.selected){
// Cancel so undo shortcut effectively undoes the current transform, unless requesting multiple steps
if (state.selectionTransformed() && undo.n<=1)
undo.cancel();
state.reset(false);
}
}
state.redocb= (redo)=>{
if (state.selected){ state.reset(false); }
}
// Mirroring // Mirroring
state.togglemirror = () => { state.togglemirror = () => {
@ -251,94 +297,70 @@ const selectTransformTool = () =>
// Handles left mouse clicks // Handles left mouse clicks
state.clickcb = (evn) => { state.clickcb = (evn) => {
if ( if (state.selectionTransformed()) {
state.selected && state.applyTransform();
!(
state.selected.rotation === 0 &&
state.selected.scale.x === 1 &&
state.selected.scale.y === 1 &&
state.selected.position.x === state.original.sx &&
state.selected.position.y === state.original.sy &&
!state.mirrorSelection &&
state.original.layer === uil.layer
) &&
!isCanvasBlank(
0,
0,
state.selected.canvas.width,
state.selected.canvas.height,
state.selected.canvas
)
) {
// Put original image back
state.original.layer.ctx.drawImage(
state.selected.canvas,
state.original.x,
state.original.y
);
// Erase Original Selection Area
commands.runCommand(
"eraseImage",
"Transform Tool Erase",
{
layer: state.original.layer,
x: state.original.x,
y: state.original.y,
w: state.selected.canvas.width,
h: state.selected.canvas.height,
},
{
extra: {
log: `Erased original selection area at x: ${state.original.x}, y: ${state.original.y}, width: ${state.selected.canvas.width}, height: ${state.selected.canvas.height} from layer ${state.original.layer.id}`,
},
}
);
// Draw Image
const {canvas, bb} = cropCanvas(state.originalDisplayLayer.canvas, {
border: 10,
});
let commandLog = "";
const addline = (v, newline = true) => {
commandLog += v;
if (newline) commandLog += "\n";
};
addline(
`Draw selected area to x: ${bb.x}, y: ${bb.y}, width: ${bb.w}, height: ${bb.h} to layer ${state.original.layer.id}`
);
addline(
` - translation: (x: ${state.selected.position.x}, y: ${state.selected.position.y})`
);
addline(
` - rotation : ${
Math.round(1000 * ((180 * state.selected.rotation) / Math.PI)) /
1000
} degrees`,
false
);
commands.runCommand(
"drawImage",
"Transform Tool Apply",
{
image: canvas,
...bb,
},
{
extra: {
log: commandLog,
},
}
);
state.reset(true);
} else { } else {
state.reset(); state.reset();
} }
}; };
// Check if selection has been transformed in any way.
state.selectionTransformed = ()=>{
return state.selected &&
!(
state.selected.rotation === 0 &&
state.selected.scale.x === 1 &&
state.selected.scale.y === 1 &&
state.selected.position.x === state.original.sx &&
state.selected.position.y === state.original.sy &&
!state.mirrorSelection &&
state.original.layer === uil.layer
);
}
// Handles left mouse double clicks - Select All Ctrl-A
// Holding shift key - Ctrl-Shift-A
state.dclickcb = (evn) => {
// Do nothing if Ctrl Key is held for panning
if (state.selected || evn.evn.ctrlKey) return;
let shift = evn.evn.shiftKey;
// Wait so clickcb doesn't immediately deselect.
state.dclickcb_timeout = state.dclickcb_timeout ?? window.setTimeout(async ()=>{
state.dclickcb_timeout = null;
if (!state.selected && !selection.exists) {
if (shift) state.ctrlsacb(evn);
else state.ctrlacb(evn);
}
},300);
};
// Handles right mouse double clicks - Select topmost layer with content under pointer
// Holding shift key Selects the next topmost if current layer has visible content under pointer.
state.drclickcb = (evn) => {
if (state.selected) return;
// If shift key is held, and current layer is has visible pixels under pointer
// select topmost visible layer beneath the active layer
let shift = evn.evn.shiftKey
&& !uil.active.hidden
&& !isCanvasBlank(evn.x,evn.y,2,2,uil.active.canvas);
let layer = shift ? uil.active : null;
for (let l of uil.layers.toReversed()) {
if (shift) {
if (layer==l) shift = false;
}
else if (!l.hidden && !isCanvasBlank(evn.x,evn.y,2,2,l.canvas)){
layer = l;
break;
}
}
if (layer) {
uil.active=layer;
state.dclickcb_timeout = state.dclickcb_timeout ?? window.setTimeout(async ()=>{
state.dclickcb_timeout = null;
if (!state.selected && !selection.exists) { state.ctrlacb(evn); }
},300);
}
};
// Handles left mouse drag start events // Handles left mouse drag start events
state.dragstartcb = (evn) => { state.dragstartcb = (evn) => {
@ -513,33 +535,54 @@ const selectTransformTool = () =>
state.cancelcb = (evn) => { state.cancelcb = (evn) => {
state.reset(); state.reset();
}; };
state.keydowncb = (evn) => { };
// Keyboard callbacks
state.keyclickcb = (evn) => { };
// Register Delete Shortcut
state.delcb = (evn) => { state.applyTransform(true,false,false,false); };
// Register Escape Shortcut
state.escapecb = (evn) => { state.reset(false); };
// Register Shift-Delete Shortcut - Delete Outside Selection and Apply
state.sdelcb = (evn) => { state.applyTransform(false,true,false,false); };
// Keyboard callbacks (For now, they just handle the "delete" key) // Register Enter Shortcut - Apply Transform (Delegates to clickcb)
state.keydowncb = (evn) => {}; state.entercb = (evn) => { state.clickcb(evn); };
state.keyclickcb = (evn) => { // Register Ctrl-Enter Shortcut - Copy Selection to new layer, restore original
switch (evn.code) { state.ctentercb = (evn) => { state.applyTransform(false,false,true,true); };
case "Delete":
// Deletes selected area // Register Shift-Enter Shortcut - Move Selection to new layer
state.selected && state.sentercb = (evn) => { state.applyTransform(false,false,true,false); };
commands.runCommand(
"eraseImage", // Register Ctrl-Shift-Enter Shortcut - Copy Visible Selection to new layer
"Erase Area", state.sctentercb = async (evn) => {
state.selected, var selectBB =
{ state.selected.bb != undefined
extra: { ? state.selected.bb
log: `[Placeholder] Delete selected area. TODO it's also broken`, : state.backupBB;
}, const canvas = uil.getVisible(selectBB, {
} categories: ["image", "user", "select-display"],
); });
state.ctxmenu.mirrorSelectionCheckbox.disabled = true; await commands.runCommand("addLayer", "Added Layer");
state.selected = null;
state.redraw(); await commands.runCommand("drawImage", "Transform Tool Apply",
} {
image: canvas,
...selectBB,
}
);
state.reset(false);
}; };
// Register Ctrl-A Shortcut // Register Ctrl-A Shortcut
state.ctrlacb = () => { state.ctrlacb = () => {
state.reset(false); // Reset to preserve selected content
try { try {
const {bb} = cropCanvas(uil.canvas); const {bb} = cropCanvas(uil.canvas);
select(bb); select(bb);
@ -549,6 +592,8 @@ const selectTransformTool = () =>
}; };
state.ctrlsacb = () => { state.ctrlsacb = () => {
state.reset(false); // Reset to preserve selected content
// Shift Key selects based on all visible layer information // Shift Key selects based on all visible layer information
const tl = {x: Infinity, y: Infinity}; const tl = {x: Infinity, y: Infinity};
const br = {x: -Infinity, y: -Infinity}; const br = {x: -Infinity, y: -Infinity};
@ -667,6 +712,111 @@ const selectTransformTool = () =>
state.ctrlxcb = (evn) => { state.ctrlxcb = (evn) => {
state.ctrlccb(evn, true); state.ctrlccb(evn, true);
}; };
// Apply Transform and Reset State, optionally erase Selection or Clear Original Layer
// newLayer defaults to null, overriding the forced variants if explicitly set to false
// Only checks if Selection exists and content has been selected
// Does not check if content has been transformed, eg for deletion/applying to new layer
state.applyTransform = async (eraseSelected = false, clearLayer = false, newLayer = null, keepOriginal = false) => {
const isBlank = !state.selected ||
isCanvasBlank( 0, 0, state.selected.canvas.width, state.selected.canvas.height, state.selected.canvas);
// Just reset state if nothing is selected, unless Clearing layer
if (!state.selected || !clearLayer && isBlank ){
state.reset(false);
return;
}
// Put original image back
state.original.layer.ctx.drawImage(
state.selected.canvas,
state.original.x,
state.original.y
);
// Erase Entire Layer
if (clearLayer) await commands.runCommand(
"eraseImage",
"Transform Tool Erase",
{
...state.original.layer.bb,
layer: state.original.layer,
},
{
extra: {
log: `Erased layer ${state.original.layer.id}`,
},
}
);
// Erase Original Selection Area
else if (eraseSelected || !keepOriginal) await commands.runCommand(
"eraseImage",
"Transform Tool Erase",
{
layer: state.original.layer,
x: state.original.x,
y: state.original.y,
w: state.selected.canvas.width,
h: state.selected.canvas.height,
},
{
extra: {
log: `Erased original selection area at x: ${state.original.x}, y: ${state.original.y}, width: ${state.selected.canvas.width}, height: ${state.selected.canvas.height} from layer ${state.original.layer.id}`,
},
}
);
// Selection erased or was blank, no need to draw anything
if (eraseSelected || isBlank){
state.reset(true);
return;
}
// Draw Image
const {canvas, bb} = cropCanvas(state.originalDisplayLayer.canvas, {
border: 10,
});
if ( (newLayer ?? state.toNewLayer) && !clearLayer)
await commands.runCommand("addLayer", "Added Layer");
let commandLog = "";
const addline = (v, newline = true) => {
commandLog += v;
if (newline) commandLog += "\n";
};
addline(
`Draw selected area to x: ${bb.x}, y: ${bb.y}, width: ${bb.w}, height: ${bb.h} to layer ${state.original.layer.id}`
);
addline(
` - translation: (x: ${state.selected.position.x}, y: ${state.selected.position.y})`
);
addline(
` - rotation : ${
Math.round(1000 * ((180 * state.selected.rotation) / Math.PI)) /
1000
} degrees`,
false
);
await commands.runCommand(
"drawImage",
"Transform Tool Apply",
{
image: canvas,
...bb,
},
{
extra: {
log: commandLog,
},
}
);
state.reset(true);
}
}, },
populateContextMenu: (menu, state) => { populateContextMenu: (menu, state) => {
if (!state.ctxmenu) { if (!state.ctxmenu) {
@ -732,6 +882,15 @@ const selectTransformTool = () =>
state.ctxmenu.useClipboardLabel = clipboardCheckbox.checkbox; state.ctxmenu.useClipboardLabel = clipboardCheckbox.checkbox;
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
// toNewLayer
state.ctxmenu.toNewLayerLabel = _toolbar_input.checkbox(
state,
"openoutpaint/select-toNewLayer",
"toNewLayer",
"Always Create New Layer",
"icon-file-plus"
).checkbox;
// Selection Peek Opacity // Selection Peek Opacity
state.ctxmenu.selectionPeekOpacitySlider = _toolbar_input.slider( state.ctxmenu.selectionPeekOpacitySlider = _toolbar_input.slider(
@ -757,7 +916,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 Sel."; saveSelectionButton.innerHTML = "Save&nbsp;Sel."; // nbsp as a quick hack for unwanted text wrapping
saveSelectionButton.title = "Saves Selection"; saveSelectionButton.title = "Saves Selection";
saveSelectionButton.onclick = () => { saveSelectionButton.onclick = () => {
downloadCanvas({ downloadCanvas({
@ -779,9 +938,25 @@ const selectTransformTool = () =>
tools.stamp.enable(); tools.stamp.enable();
}; };
}; };
const copyNewLayerButton = document.createElement("button");
copyNewLayerButton.classList.add("button", "tool");
copyNewLayerButton.textContent = "Layer";
copyNewLayerButton.title = "Copies selection to a new Layer (Ctrl+Enter)";
copyNewLayerButton.onclick = () => { state.applyTransform(false,false,true,true); };
// Dummy button for saving active selection
const ActiveSelectionButton = document.createElement("button");
ActiveSelectionButton.classList.add("button", "tool");
ActiveSelectionButton.textContent = "📄";
ActiveSelectionButton.title = "Commands Applied to the Current Selection";
ActiveSelectionButton.disabled = true;
actionArray.appendChild(saveSelectionButton); actionArray.appendChild(saveSelectionButton);
actionArray.appendChild(createResourceButton); actionArray.appendChild(createResourceButton);
actionArray.appendChild(copyNewLayerButton);
actionArray.appendChild(ActiveSelectionButton);
// Some useful actions to do with selection // Some useful actions to do with selection
const visibleActionArray = document.createElement("div"); const visibleActionArray = document.createElement("div");
@ -790,8 +965,8 @@ const selectTransformTool = () =>
// Save Visible button // Save Visible button
const saveVisibleSelectionButton = document.createElement("button"); const saveVisibleSelectionButton = document.createElement("button");
saveVisibleSelectionButton.classList.add("button", "tool"); saveVisibleSelectionButton.classList.add("button", "tool");
saveVisibleSelectionButton.textContent = "Save Vis."; saveVisibleSelectionButton.innerHTML = "Save&nbsp;Vis."; // nbsp as a quick hack for unwanted text wrapping
saveVisibleSelectionButton.title = "Saves Visible Selection"; saveVisibleSelectionButton.title = "Saves Visible Selection And Download";
saveVisibleSelectionButton.onclick = () => { saveVisibleSelectionButton.onclick = () => {
console.debug(state.selected); console.debug(state.selected);
console.debug(state.selected.bb); console.debug(state.selected.bb);
@ -811,7 +986,7 @@ const selectTransformTool = () =>
// Save Visible as Resource Button // Save Visible as Resource Button
const createVisibleResourceButton = document.createElement("button"); const createVisibleResourceButton = document.createElement("button");
createVisibleResourceButton.classList.add("button", "tool"); createVisibleResourceButton.classList.add("button", "tool");
createVisibleResourceButton.textContent = "Vis. to Res."; createVisibleResourceButton.textContent = "Resource";
createVisibleResourceButton.title = createVisibleResourceButton.title =
"Saves Visible Selection as a Resource"; "Saves Visible Selection as a Resource";
createVisibleResourceButton.onclick = () => { createVisibleResourceButton.onclick = () => {
@ -830,8 +1005,52 @@ const selectTransformTool = () =>
}; };
}; };
// Copy To Layer Buttons
const copyVisNewLayerButton = document.createElement("button");
copyVisNewLayerButton.classList.add("button", "tool");
copyVisNewLayerButton.textContent = "Layer";
copyVisNewLayerButton.title = "Copies Visible Selection to a new Layer (Ctrl+Shift+Enter)";
copyVisNewLayerButton.onclick = (e) => { state.sctentercb(e); };
// Dummy button for saving visible Selection
const VisibleSelectionButton = document.createElement("button");
VisibleSelectionButton.classList.add("button", "tool");
VisibleSelectionButton.textContent = "👁";
VisibleSelectionButton.title = "Commands Applied to All Visible Content In the Selected Area";
VisibleSelectionButton.disabled = true;
visibleActionArray.appendChild(saveVisibleSelectionButton); visibleActionArray.appendChild(saveVisibleSelectionButton);
visibleActionArray.appendChild(createVisibleResourceButton); visibleActionArray.appendChild(createVisibleResourceButton);
visibleActionArray.appendChild(copyVisNewLayerButton);
visibleActionArray.appendChild(VisibleSelectionButton);
const actionArrayRow3 = document.createElement("div");
actionArrayRow3.classList.add("button-array");
// Clear Button
const applyClearButton = document.createElement("button");
applyClearButton.classList.add("button", "tool");
applyClearButton.textContent = "Isolate";
applyClearButton.title = "Erases everything in the current layer outside the selection (Shift+Delete)";
applyClearButton.onclick = () => { state.applyTransform(false,true,false,false); };
// Erase Button
const eraseSelectionButton = document.createElement("button");
eraseSelectionButton.classList.add("button", "tool");
eraseSelectionButton.textContent = "Erase";
eraseSelectionButton.title = "Erases current selection (Delete)";
eraseSelectionButton.onclick = () => { state.applyTransform(true,false,false,false); };
// Apply To New Layer button
const applyNewLayerButton = document.createElement("button");
applyNewLayerButton.classList.add("button", "tool");
applyNewLayerButton.textContent = "Extract";
applyNewLayerButton.title = "Moves Selection to a New Layer (Shift+Enter)";
applyNewLayerButton.onclick = () => { state.applyTransform(false,false,true,false); };
actionArrayRow3.appendChild(applyClearButton);
actionArrayRow3.appendChild(eraseSelectionButton);
actionArrayRow3.appendChild(applyNewLayerButton);
// Disable buttons (if nothing is selected) // Disable buttons (if nothing is selected)
state.ctxmenu.disableButtons = () => { state.ctxmenu.disableButtons = () => {
@ -839,6 +1058,11 @@ const selectTransformTool = () =>
createResourceButton.disabled = true; createResourceButton.disabled = true;
saveVisibleSelectionButton.disabled = true; saveVisibleSelectionButton.disabled = true;
createVisibleResourceButton.disabled = true; createVisibleResourceButton.disabled = true;
applyNewLayerButton.disabled = true;
copyNewLayerButton.disabled = true;
applyClearButton.disabled = true;
eraseSelectionButton.disabled = true;
copyVisNewLayerButton.disabled = true;
}; };
// Enable buttons (if something is selected) // Enable buttons (if something is selected)
@ -847,9 +1071,17 @@ const selectTransformTool = () =>
createResourceButton.disabled = ""; createResourceButton.disabled = "";
saveVisibleSelectionButton.disabled = ""; saveVisibleSelectionButton.disabled = "";
createVisibleResourceButton.disabled = ""; createVisibleResourceButton.disabled = "";
applyNewLayerButton.disabled = "";
copyNewLayerButton.disabled = "";
applyClearButton.disabled = "";
eraseSelectionButton.disabled = "";
copyVisNewLayerButton.disabled = "";
}; };
state.ctxmenu.actionArray = actionArray; state.ctxmenu.actionArray = actionArray;
state.ctxmenu.visibleActionArray = visibleActionArray; state.ctxmenu.visibleActionArray = visibleActionArray;
state.ctxmenu.actionArrayRow3 = actionArrayRow3;
// Send Selection to Destination // Send Selection to Destination
state.ctxmenu.sendSelected = document.createElement("select"); state.ctxmenu.sendSelected = document.createElement("select");
@ -872,10 +1104,15 @@ const selectTransformTool = () =>
array.appendChild(state.ctxmenu.keepAspectRatioLabel); array.appendChild(state.ctxmenu.keepAspectRatioLabel);
array.appendChild(state.ctxmenu.mirrorSelectionCheckbox); array.appendChild(state.ctxmenu.mirrorSelectionCheckbox);
array.appendChild(state.ctxmenu.useClipboardLabel); array.appendChild(state.ctxmenu.useClipboardLabel);
array.appendChild(state.ctxmenu.toNewLayerLabel);
menu.appendChild(array); menu.appendChild(array);
menu.appendChild(state.ctxmenu.selectionPeekOpacitySlider); menu.appendChild(state.ctxmenu.selectionPeekOpacitySlider);
menu.appendChild(state.ctxmenu.actionArray); menu.appendChild(state.ctxmenu.actionArray);
menu.appendChild(state.ctxmenu.visibleActionArray); menu.appendChild(state.ctxmenu.visibleActionArray);
menu.appendChild(state.ctxmenu.actionArrayRow3);
if (global.webui && global.webui.destinations) { if (global.webui && global.webui.destinations) {
while (state.ctxmenu.sendSelected.lastChild.value !== "None") { while (state.ctxmenu.sendSelected.lastChild.value !== "None") {
state.ctxmenu.sendSelected.removeChild( state.ctxmenu.sendSelected.removeChild(

View file

@ -8,7 +8,7 @@
<iframe <iframe
id="openoutpaint" id="openoutpaint"
style="width: 100%; height: 800px" style="width: 100%; height: 800px"
src="../index.html?v=34d5675" src="../index.html?v=f393a6b"
frameborder="0"></iframe> frameborder="0"></iframe>
<button id="add-res">Add Resource</button> <button id="add-res">Add Resource</button>
<script> <script>

9
test.html Normal file
View file

@ -0,0 +1,9 @@
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Test</h1>
<p>Test</p>
</body>
</html>