diff --git a/README.md b/README.md
index 2f6dffb..dff03ac 100644
--- a/README.md
+++ b/README.md
@@ -70,8 +70,11 @@ Note, you can right-click on a bunch of the rgthree-comfy nodes and select `đ
>
> âšī¸ See More Information
>
-> - Define the `sahortvut_key` to press to go right to that bookmark node, anchored in the top left
+> - Define the `shortcut_key` to press to go right to that bookmark node, anchored in the top left.
> - You can also define the zoom level as well!
+> - Pro tip: `shortcut_key` can be multiple keys. For instance "alt + shift + !" would require
+> pressing the alt key, the shift key, and the "!" (as in the "1" key, but with shift pressed)
+> in order to trigger.
>
diff --git a/src_web/comfyui/bookmark.ts b/src_web/comfyui/bookmark.ts
index 06be5a5..b931b13 100644
--- a/src_web/comfyui/bookmark.ts
+++ b/src_web/comfyui/bookmark.ts
@@ -2,15 +2,15 @@ import { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js";
// @ts-ignore
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
+import { rgthree } from "./rgthree.js";
import { NodeTypesString } from "./constants.js";
import type {
LGraph as TLGraph,
LiteGraph as TLiteGraph,
LGraphCanvas as TLGraphCanvas,
- ISliderWidget,
INumberWidget,
- Vector2,
} from "typings/litegraph.js";
+import { getClosestOrSelf } from "rgthree/common/utils_dom.js";
declare const LiteGraph: typeof TLiteGraph;
@@ -19,7 +19,6 @@ declare const LiteGraph: typeof TLiteGraph;
* navigate to that node, with it in the top-left corner.
*/
export class Bookmark extends RgthreeBaseVirtualNode {
-
static override type = NodeTypesString.BOOKMARK;
static override title = NodeTypesString.BOOKMARK;
override comfyClass = NodeTypesString.BOOKMARK;
@@ -32,7 +31,6 @@ export class Bookmark extends RgthreeBaseVirtualNode {
// override it with a setter and re-set it measured exactly as we want.
___collapsed_width: number = 0;
-
override isVirtualNode = true;
override serialize_widgets = true;
@@ -43,10 +41,10 @@ export class Bookmark extends RgthreeBaseVirtualNode {
override set _collapsed_width(width: number) {
const canvas = app.canvas as TLGraphCanvas;
- const ctx = canvas.canvas.getContext('2d')!;
+ const ctx = canvas.canvas.getContext("2d")!;
const oldFont = ctx.font;
ctx.font = canvas.title_text_font;
- this.___collapsed_width = 40 + ctx.measureText(this.title).width;
+ this.___collapsed_width = 40 + ctx.measureText(this.title).width;
ctx.font = oldFont;
}
@@ -54,14 +52,18 @@ export class Bookmark extends RgthreeBaseVirtualNode {
constructor(title = Bookmark.title) {
super(title);
- this.addWidget('text', 'shortcut_key', '1', (value: string, ...args) => {
- value = value.trim()[0] || '1';
- },{
- y: 8,
- });
- this.addWidget('number', 'zoom', 1, (value: number) => {
-
- }, {
+ this.addWidget(
+ "text",
+ "shortcut_key",
+ "1",
+ (value: string, ...args) => {
+ value = value.trim()[0] || "1";
+ },
+ {
+ y: 8,
+ },
+ );
+ this.addWidget("number", "zoom", 1, (value: number) => {}, {
y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4,
max: 2,
min: 0.5,
@@ -78,28 +80,29 @@ export class Bookmark extends RgthreeBaseVirtualNode {
// }
get shortcutKey(): string {
- return this.widgets[0]?.value?.toLocaleLowerCase() ?? '';
+ return this.widgets[0]?.value?.toLocaleLowerCase() ?? "";
}
override onAdded(graph: TLGraph): void {
- window.addEventListener("keydown", this.keypressBound);
+ rgthree.addEventListener("keydown", this.keypressBound as EventListener);
}
override onRemoved(): void {
- window.removeEventListener("keydown", this.keypressBound);
+ rgthree.removeEventListener("keydown", this.keypressBound as EventListener);
}
- async onKeypress(event: KeyboardEvent) {
- const target = (event.target as HTMLElement)!;
- // Span because the properties panel uses a contenteditable
- if (['input','textarea', 'span'].includes(target.localName)) {
+ onKeypress(event: CustomEvent<{ originalEvent: KeyboardEvent }>) {
+ const originalEvent = event.detail.originalEvent;
+ const target = (originalEvent.target as HTMLElement)!;
+ if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
return;
}
- if (event.ctrlKey || event.metaKey || event.altKey) {
- return;
- }
- if (event.key.toLocaleLowerCase() === this.shortcutKey) {
+
+ // Only the shortcut keys are held down, otionally including "shift".
+ if (rgthree.areOnlyKeysDown(this.widgets[0]!.value, true)) {
this.canvasToBookmark();
+ originalEvent.preventDefault();
+ originalEvent.stopPropagation();
}
}
@@ -108,8 +111,8 @@ export class Bookmark extends RgthreeBaseVirtualNode {
// ComfyUI seemed to break us again, but couldn't repro. No reason to not check, I guess.
// https://github.com/rgthree/rgthree-comfy/issues/71
if (canvas?.ds?.offset) {
- canvas.ds.offset[0] = -this.pos[0] + 16;
- canvas.ds.offset[1] = -this.pos[1] + 40;
+ canvas.ds.offset[0] = -this.pos[0] + 16;
+ canvas.ds.offset[1] = -this.pos[1] + 40;
}
if (canvas?.ds?.scale != null) {
canvas.ds.scale = Number(this.widgets[1]!.value || 1);
diff --git a/src_web/comfyui/rgthree.ts b/src_web/comfyui/rgthree.ts
index c7e39d2..9311694 100644
--- a/src_web/comfyui/rgthree.ts
+++ b/src_web/comfyui/rgthree.ts
@@ -22,7 +22,7 @@ import { NodeTypesString } from "./constants.js";
import { RgthreeProgressBar } from "rgthree/common/progress_bar.js";
import { RgthreeConfigDialog } from "./config.js";
import { iconGear, iconReplace, iconStarFilled, logoRgthree } from "rgthree/common/media/svgs.js";
-import type { Bookmark } from './bookmark';
+import type { Bookmark } from "./bookmark";
declare const LiteGraph: typeof TLiteGraph;
declare const LGraphCanvas: typeof TLGraphCanvas;
@@ -121,14 +121,12 @@ class Logger {
}
}
-
/**
* A log session, with the name as the prefix. A new session will stack prefixes.
*/
class LogSession {
-
readonly logger = new Logger();
- readonly logsCache: {[key: string]: {lastShownTime: number}} = {};
+ readonly logsCache: { [key: string]: { lastShownTime: number } } = {};
constructor(readonly name?: string) {}
@@ -139,16 +137,17 @@ class LogSession {
* const [logMethod, logArgs] = logger.logParts(LogLevel.DEBUG, message, ...args);
* console[logMethod]?.(...logArgs);
*/
- logParts(level: LogLevel, message?: string, ...args: any[]) : [ConsoleLogFns, any[]] {
+ logParts(level: LogLevel, message?: string, ...args: any[]): [ConsoleLogFns, any[]] {
message = `${this.name || ""}${message ? " " + message : ""}`;
- return this.logger.logParts(
- level,
- message,
- ...args,
- );
+ return this.logger.logParts(level, message, ...args);
}
- logPartsOnceForTime(level: LogLevel, time: number, message?: string, ...args: any[]) : [ConsoleLogFns, any[]] {
+ logPartsOnceForTime(
+ level: LogLevel,
+ time: number,
+ message?: string,
+ ...args: any[]
+ ): [ConsoleLogFns, any[]] {
message = `${this.name || ""}${message ? " " + message : ""}`;
const cacheKey = `${level}:${message}`;
const cacheEntry = this.logsCache[cacheKey];
@@ -156,17 +155,12 @@ class LogSession {
if (cacheEntry && cacheEntry.lastShownTime + time > now) {
return ["none" as "info", []];
}
- const parts = this.logger.logParts(
- level,
- message,
- ...args,
- );
+ const parts = this.logger.logParts(level, message, ...args);
if (console[parts[0]]) {
- this.logsCache[cacheKey] = this.logsCache[cacheKey] || {} as {lastShownTime: number};
+ this.logsCache[cacheKey] = this.logsCache[cacheKey] || ({} as { lastShownTime: number });
this.logsCache[cacheKey]!.lastShownTime = now;
}
return parts;
-
}
debugParts(message?: string, ...args: any[]) {
@@ -251,6 +245,18 @@ class Rgthree extends EventTarget {
this.handleKeyup(e);
});
+ // If we get a visibilitychange, then clear the keys since we can't listen for keys up/down when
+ // not visible.
+ document.addEventListener("visibilitychange", (e) => {
+ this.clearKeydowns();
+ });
+
+ // If we get a blur, then also clear the keys since we can't listen for keys up/down when
+ // blurred. This can happen w/o a visibilitychange, like a browser alert.
+ window.addEventListener("blur", (e) => {
+ this.clearKeydowns();
+ });
+
this.initializeGraphAndCanvasHooks();
this.initializeComfyUIHooks();
this.initializeContextMenu();
@@ -852,8 +858,19 @@ class Rgthree extends EventTarget {
container && (container.innerHTML = "");
}
+ private clearKeydowns() {
+ this.ctrlKey = false;
+ this.altKey = false;
+ this.metaKey = false;
+ this.shiftKey = false;
+ for (const key in this.downKeys) delete this.downKeys[key];
+ }
+
/**
- * Handle keydown. Pulled out because sometimes a node will get a keydown before rgthree.
+ * Handle keydown. Pulled out because sometimes a node will get a keydown before rgthree and call
+ * into this..
+ *
+ * Note: ComfyUI blocks Space, Esc, Delete, and Backspace.
*/
handleKeydown(e: KeyboardEvent) {
this.ctrlKey = !!e.ctrlKey;
@@ -861,33 +878,78 @@ class Rgthree extends EventTarget {
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
this.downKeys[e.key.toLocaleUpperCase()] = true;
- this.downKeys["^" + e.key.toLocaleUpperCase()] = true;
+ this.dispatchCustomEvent("keydown", { originalEvent: e });
}
/**
- * Handle keyup. Pulled out because sometimes a node will get a keyup before rgthree.
+ * Handle keyup. Pulled out because sometimes a node will get a keyup before rgthree and call
+ * into this.
*/
handleKeyup(e: KeyboardEvent) {
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
- this.downKeys[e.key.toLocaleUpperCase()] = false;
- this.downKeys["^" + e.key.toLocaleUpperCase()] = false;
+ delete this.downKeys[e.key.toLocaleUpperCase()];
+ this.dispatchCustomEvent("keyup", { originalEvent: e });
+ }
+
+ /**
+ * Parses a shortcut string.
+ *
+ * - 's' => ['S']
+ * - 'shift + c' => ['SHIFT', 'C']
+ * - 'shift + meta + @' => ['SHIFT', 'META', '@']
+ * - 'shift + + + @' => ['SHIFT', '__PLUS__', '=']
+ * - '+ + p' => ['__PLUS__', 'P']
+ */
+ private getKeysFromShortcut(shortcut: string | string[]) {
+ let keys;
+ if (typeof shortcut === "string") {
+ // Rip all spaces out. Note, Comfy swallows space, so we don't have to handle it. Otherwise,
+ // we would require space to be fed as "Space" or "Spacebar" instead of " ".
+ shortcut = shortcut.replace(/\s/g, "");
+ // Change a real "+" to something we can encode.
+ shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
+ keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
+ } else {
+ keys = [...shortcut];
+ }
+ return keys.map((k) => k.toLocaleUpperCase());
}
/**
* Checks if all keys passed in are down.
*/
- areAllKeysDown(keys: string[], caseSensitive = false) {
+ areAllKeysDown(keys: string | string[]) {
+ keys = this.getKeysFromShortcut(keys);
return keys.every((k) => {
- if (caseSensitive) {
- return rgthree.downKeys["^" + k.trim()];
- }
- return rgthree.downKeys[k.trim().toUpperCase()];
+ return rgthree.downKeys[k];
});
}
+ /**
+ * Checks if only the keys passed in are down; optionally and additionally allowing "shift" key.
+ */
+ areOnlyKeysDown(keys: string | string[], alsoAllowShift = false) {
+ keys = this.getKeysFromShortcut(keys);
+ const allKeysDown = this.areAllKeysDown(keys);
+ const downKeysLength = Object.values(rgthree.downKeys).length;
+ // All keys are down and they're the only ones.
+ if (allKeysDown && keys.length === downKeysLength) {
+ return true;
+ }
+ // Special case allowing the shift key in addition to the shortcut keys. This helps when a user
+ // may had originally defined "$" as a shortcut, but needs to press "shift + $" since it's an
+ // upper key character, etc.
+ if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
+ // If we're holding down shift, have one extra key held down, and the original keys don't
+ // include shift, then we're good to go.
+ return this.areAllKeysDown(["SHIFT"]);
+ }
+ return false;
+ }
+
/**
* Injects the rgthree.css file into the app.
*/
diff --git a/web/comfyui/bookmark.js b/web/comfyui/bookmark.js
index cfad7ec..7d2c338 100644
--- a/web/comfyui/bookmark.js
+++ b/web/comfyui/bookmark.js
@@ -1,13 +1,15 @@
import { app } from "../../scripts/app.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
+import { rgthree } from "./rgthree.js";
import { NodeTypesString } from "./constants.js";
+import { getClosestOrSelf } from "../../rgthree/common/utils_dom.js";
export class Bookmark extends RgthreeBaseVirtualNode {
get _collapsed_width() {
return this.___collapsed_width;
}
set _collapsed_width(width) {
const canvas = app.canvas;
- const ctx = canvas.canvas.getContext('2d');
+ const ctx = canvas.canvas.getContext("2d");
const oldFont = ctx.font;
ctx.font = canvas.title_text_font;
this.___collapsed_width = 40 + ctx.measureText(this.title).width;
@@ -19,13 +21,12 @@ export class Bookmark extends RgthreeBaseVirtualNode {
this.___collapsed_width = 0;
this.isVirtualNode = true;
this.serialize_widgets = true;
- this.addWidget('text', 'shortcut_key', '1', (value, ...args) => {
- value = value.trim()[0] || '1';
+ this.addWidget("text", "shortcut_key", "1", (value, ...args) => {
+ value = value.trim()[0] || "1";
}, {
y: 8,
});
- this.addWidget('number', 'zoom', 1, (value) => {
- }, {
+ this.addWidget("number", "zoom", 1, (value) => { }, {
y: 8 + LiteGraph.NODE_WIDGET_HEIGHT + 4,
max: 2,
min: 0.5,
@@ -36,24 +37,24 @@ export class Bookmark extends RgthreeBaseVirtualNode {
}
get shortcutKey() {
var _a, _b, _c;
- return (_c = (_b = (_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.toLocaleLowerCase()) !== null && _c !== void 0 ? _c : '';
+ return (_c = (_b = (_a = this.widgets[0]) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.toLocaleLowerCase()) !== null && _c !== void 0 ? _c : "";
}
onAdded(graph) {
- window.addEventListener("keydown", this.keypressBound);
+ rgthree.addEventListener("keydown", this.keypressBound);
}
onRemoved() {
- window.removeEventListener("keydown", this.keypressBound);
+ rgthree.removeEventListener("keydown", this.keypressBound);
}
- async onKeypress(event) {
- const target = event.target;
- if (['input', 'textarea', 'span'].includes(target.localName)) {
+ onKeypress(event) {
+ const originalEvent = event.detail.originalEvent;
+ const target = originalEvent.target;
+ if (getClosestOrSelf(target, 'input,textarea,[contenteditable="true"]')) {
return;
}
- if (event.ctrlKey || event.metaKey || event.altKey) {
- return;
- }
- if (event.key.toLocaleLowerCase() === this.shortcutKey) {
+ if (rgthree.areOnlyKeysDown(this.widgets[0].value, true)) {
this.canvasToBookmark();
+ originalEvent.preventDefault();
+ originalEvent.stopPropagation();
}
}
canvasToBookmark() {
diff --git a/web/comfyui/rgthree.js b/web/comfyui/rgthree.js
index 3c9e5f5..9a980c0 100644
--- a/web/comfyui/rgthree.js
+++ b/web/comfyui/rgthree.js
@@ -138,6 +138,12 @@ class Rgthree extends EventTarget {
window.addEventListener("keyup", (e) => {
this.handleKeyup(e);
});
+ document.addEventListener("visibilitychange", (e) => {
+ this.clearKeydowns();
+ });
+ window.addEventListener("blur", (e) => {
+ this.clearKeydowns();
+ });
this.initializeGraphAndCanvasHooks();
this.initializeComfyUIHooks();
this.initializeContextMenu();
@@ -584,30 +590,60 @@ class Rgthree extends EventTarget {
let container = document.querySelector(".rgthree-top-messages-container");
container && (container.innerHTML = "");
}
+ clearKeydowns() {
+ this.ctrlKey = false;
+ this.altKey = false;
+ this.metaKey = false;
+ this.shiftKey = false;
+ for (const key in this.downKeys)
+ delete this.downKeys[key];
+ }
handleKeydown(e) {
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
this.downKeys[e.key.toLocaleUpperCase()] = true;
- this.downKeys["^" + e.key.toLocaleUpperCase()] = true;
+ this.dispatchCustomEvent("keydown", { originalEvent: e });
}
handleKeyup(e) {
this.ctrlKey = !!e.ctrlKey;
this.altKey = !!e.altKey;
this.metaKey = !!e.metaKey;
this.shiftKey = !!e.shiftKey;
- this.downKeys[e.key.toLocaleUpperCase()] = false;
- this.downKeys["^" + e.key.toLocaleUpperCase()] = false;
+ delete this.downKeys[e.key.toLocaleUpperCase()];
+ this.dispatchCustomEvent("keyup", { originalEvent: e });
}
- areAllKeysDown(keys, caseSensitive = false) {
+ getKeysFromShortcut(shortcut) {
+ let keys;
+ if (typeof shortcut === "string") {
+ shortcut = shortcut.replace(/\s/g, "");
+ shortcut = shortcut.replace(/^\+/, "__PLUS__").replace(/\+\+/, "+__PLUS__");
+ keys = shortcut.split("+").map((i) => i.replace("__PLUS__", "+"));
+ }
+ else {
+ keys = [...shortcut];
+ }
+ return keys.map((k) => k.toLocaleUpperCase());
+ }
+ areAllKeysDown(keys) {
+ keys = this.getKeysFromShortcut(keys);
return keys.every((k) => {
- if (caseSensitive) {
- return rgthree.downKeys["^" + k.trim()];
- }
- return rgthree.downKeys[k.trim().toUpperCase()];
+ return rgthree.downKeys[k];
});
}
+ areOnlyKeysDown(keys, alsoAllowShift = false) {
+ keys = this.getKeysFromShortcut(keys);
+ const allKeysDown = this.areAllKeysDown(keys);
+ const downKeysLength = Object.values(rgthree.downKeys).length;
+ if (allKeysDown && keys.length === downKeysLength) {
+ return true;
+ }
+ if (alsoAllowShift && !keys.includes("SHIFT") && keys.length === downKeysLength - 1) {
+ return this.areAllKeysDown(["SHIFT"]);
+ }
+ return false;
+ }
injectRgthreeCss() {
let link = document.createElement("link");
link.rel = "stylesheet";