Skip to content

Commit

Permalink
merged ideas of pythongossss PR #3842 https://github.com/comfyanonymo…
Browse files Browse the repository at this point in the history
…us/ComfyUI/pull/3842/files to maintain zero core changes for tooltip support
  • Loading branch information
Amorano committed Jun 23, 2024
1 parent 11cc287 commit 7faa0d2
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 284 deletions.
37 changes: 23 additions & 14 deletions core/calc.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,9 @@ def INPUT_TYPES(cls) -> dict:
#"min": -sys.maxsize, "max": sys.maxsize,
"label": [Lexicon.X, Lexicon.Y, Lexicon.Z, Lexicon.W],
"tooltip":"default value vector for B"}),
},
"outputs": {
Lexicon.ANY_OUT: {"tooltip":"testing the output tool tips"}
}
})
return Lexicon._parse(d, cls)
Expand Down Expand Up @@ -695,12 +698,16 @@ def INPUT_TYPES(cls) -> dict:
d.update({
"optional": {
# data to pass on a pulse of the loop
Lexicon.TRIGGER: (WILDCARD, {"default": None, "tooltip":"Output to send when beat (BPM setting) is hit"}),
Lexicon.TRIGGER: (WILDCARD, {"default": None,
"tooltip":"Output to send when beat (BPM setting) is hit"}),
# forces a MOD on CYCLE
Lexicon.VALUE: ("INT", {"min": 0, "default": 0, "step": 1, "tooltip": "the current frame number of the tick"}),
Lexicon.LOOP: ("INT", {"min": 0, "default": 0, "step": 1, "tooltip": "number of frames before looping starts. 0 means continuous playback (no loop point)"}),
Lexicon.VALUE: ("INT", {"min": 0, "default": 0, "step": 1,
"tooltip": "the current frame number of the tick"}),
Lexicon.LOOP: ("INT", {"min": 0, "default": 0, "step": 1,
"tooltip": "number of frames before looping starts. 0 means continuous playback (no loop point)"}),
#
Lexicon.FPS: ("INT", {"min": 1, "default": 24, "step": 1, "tooltip": "Fixed frame step rate based on FPS (1/FPS)"}),
Lexicon.FPS: ("INT", {"min": 1, "default": 24, "step": 1,
"tooltip": "Fixed frame step rate based on FPS (1/FPS)"}),
Lexicon.BPM: ("FLOAT", {"min": 1, "max": 60000, "default": 120, "step": 1,
"tooltip": "BPM trigger rate to send the input. If input is empty, TRUE is sent on trigger"}),
Lexicon.NOTE: ("INT", {"default": 4, "min": 1, "max": 256, "step": 1,
Expand Down Expand Up @@ -793,8 +800,10 @@ def INPUT_TYPES(cls) -> dict:
except: pass
d.update({
"optional": {
Lexicon.IN_A: (WILDCARD, {"default": None, "tooltip":"Passes a raw value directly, or supplies defaults for any value inputs without connections"}),
Lexicon.TYPE: (typ, {"default": EnumConvertType.BOOLEAN.name}),
Lexicon.IN_A: (WILDCARD, {"default": None,
"tooltip":"Passes a raw value directly, or supplies defaults for any value inputs without connections"}),
Lexicon.TYPE: (typ, {"default": EnumConvertType.BOOLEAN.name,
"tooltip":"Take the input and convert it into the selected type."}),
Lexicon.X: (WILDCARD, {"default": 0, "min": -sys.maxsize,
"max": sys.maxsize, "step": 0.01, "precision": 6,
"forceInput": True}),
Expand All @@ -808,14 +817,14 @@ def INPUT_TYPES(cls) -> dict:
"max": sys.maxsize, "step": 0.01, "precision": 6,
"forceInput": True}),
Lexicon.IN_A+Lexicon.IN_A: ("VEC4", {"default": (0, 0, 0, 0),
#"min": -sys.maxsize, "max": sys.maxsize,
"label": [Lexicon.X, Lexicon.Y],
"tooltip":"default value vector for A"}),
#"min": -sys.maxsize, "max": sys.maxsize,
"label": [Lexicon.X, Lexicon.Y],
"tooltip":"default value vector for A"}),
Lexicon.SEED: ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
Lexicon.IN_B+Lexicon.IN_B: ("VEC4", {"default": (0,0,0,0),
#"min": -sys.maxsize, "max": sys.maxsize,
"label": [Lexicon.X, Lexicon.Y, Lexicon.Z, Lexicon.W],
"tooltip":"default value vector for B"}),
Lexicon.IN_B+Lexicon.IN_B: ("VEC4", {"default": (1,1,1,1),
#"min": -sys.maxsize, "max": sys.maxsize,
"label": [Lexicon.X, Lexicon.Y, Lexicon.Z, Lexicon.W],
"tooltip":"default value vector for B"}),
Lexicon.STRING: ("STRING", {"default": "", "dynamicPrompts": False, "multiline": True}),
}
})
Expand All @@ -830,7 +839,7 @@ def run(self, **kw) -> Tuple[bool]:
typ = parse_param(kw, Lexicon.TYPE, EnumConvertType.STRING, EnumConvertType.BOOLEAN.name)
xyzw = parse_param(kw, Lexicon.IN_A+Lexicon.IN_A, EnumConvertType.VEC4, (0, 0, 0, 0))
seed = parse_param(kw, Lexicon.RANDOM, EnumConvertType.INT, 0, 0)
yyzw = parse_param(kw, Lexicon.IN_B+Lexicon.IN_B, EnumConvertType.VEC4, (0, 0, 0, 0))
yyzw = parse_param(kw, Lexicon.IN_B+Lexicon.IN_B, EnumConvertType.VEC4, (1, 1, 1, 1))
x_str = parse_param(kw, Lexicon.STRING, EnumConvertType.STRING, "")
params = list(zip_longest_fill(raw, r_x, r_y, r_z, r_w, typ, xyzw, seed, yyzw, x_str))
results = []
Expand Down
9 changes: 6 additions & 3 deletions sup/lexicon.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,20 @@ def _parse(cls, node: dict, node_cls: object) -> dict:
name_url = node_cls.NAME.split(" (JOV)")[0]
url = name_url.replace(" ", "%20")
cat = node_cls.CATEGORY.split('/')[1]
data = {"_": f"{cat}#-{url}", "*": f"node/{name_url}/{name_url}.md"}
data = {"_": f"{cat}#-{url}", "*": f"node/{name_url}/{name_url}.md", "outputs": {}}
for cat, entry in node.items():
if cat not in ['optional', 'required']:
if cat not in ['optional', 'required', 'outputs']:
continue
for k, v in entry.items():
if len(v) > 1:
if (tip := v[1].get('tooltip', None)) is None:
if (tip := cls._tooltipsDB.get(k), None) is None:
logger.warning(f"no {k}")
continue
data[k] = tip
if cat == 'outputs':
data['outputs'][k] = tip
else:
data[k] = tip
if node.get("optional", None) is None:
node["optional"] = {}
node["optional"]["tooltips"] = ("JTOOLTIP", {"default": data})
Expand Down
80 changes: 1 addition & 79 deletions web/core/core_cozy_fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,14 @@
import { app } from "../../../scripts/app.js"
import * as util_config from '../util/util_config.js'
import { hex2rgb } from '../util/util_color.js'
import { $el } from "../../../scripts/ui.js"
import { node_isOverInput } from '../util/util.js'

const style = `
.tooltips {
object-fit: absolute;
width: var(--comfy-img-preview-width);
height: var(--comfy-img-preview-height);
}
`;

let g_color_style;
let g_thickness = 1;
let g_highlight;

app.registerExtension({
name: "jovimetrix.cozy.fields",
init() {
setup() {
let id = "user.default.color.style"
app.ui.settings.addSetting({
id: `jov.${id}`,
Expand Down Expand Up @@ -68,30 +58,6 @@ app.registerExtension({
},
});
},
async setup() {
$el(
'div',
{
id: "jov-tooltip",
parent: document.body,
},
[
$el("table", [
$el(
"caption",
{ textContent: "Settings" },
),
$el("button", {
type: "button",
textContent: "Close",
style: {
cursor: "pointer",
},
}),
]),
]
);
},
async nodeCreated(node) {
const onDrawForeground = node.onDrawForeground;
node.onDrawForeground = function (ctx, area) {
Expand Down Expand Up @@ -137,49 +103,5 @@ app.registerExtension({
}
return me;
}

const widget_tooltip = (node.widgets || [])
.find(widget => widget.type === 'JTOOLTIP');
if (widget_tooltip) {
let hoverTimeout = null;
let last_control = null;
const tips = widget_tooltip.options.default || {};
const hoverThreshold = 220;
const onMouseMove = node.onMouseMove;
node.onMouseMove = function (e, pos, graph) {
const me = onMouseMove?.apply(this, arguments);
if (hoverTimeout) {
clearTimeout(hoverTimeout);
hoverTimeout = null;
}

if (!node.flags.collapsed) {
const slot = node.getSlotInPosition(pos[0] + node.pos[0], pos[1] + node.pos[1]);
hoverTimeout = setTimeout(() => {
if (slot) {
if (last_control != slot) {
let tip;
if (slot.input) {
tip = tips?.[slot.input.name];
} else if (slot.output) {
tip = tips?.[slot.output.name];
} else if (slot.widgets) {
tip = tips?.[slot.widgets.name];
}
if (tip) {
console.info(tip)
//tooltip.style.left = `${e.clientX}px`;
//tooltip.style.top = `${e.clientY + 20}px`;
//tooltip.style.display = 'block';
//tooltip.innerHTML = 'Hovered over target for more than 1 second';
}
}
last_control = slot;
}
}, hoverThreshold);
}
return me;
}
}
},
})
137 changes: 137 additions & 0 deletions web/core/core_cozy_tips.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* File: cozy_tips.js
* Project: Jovimetrix
*
*/

import { app } from "../../../scripts/app.js"
import { $el } from "../../../scripts/ui.js"
import { getHoveredWidget } from '../util/util_widget.js'

const JTooltipWidget = (app, name, opts) => {
let options = opts || {};
options.serialize = false;
const w = {
name: name,
type: "JTOOLTIP",
hidden: true,
options: options,
draw: function (ctx, node, width, Y, height) {
return;
},
computeSize: function (width) {
return [width, 0];
}
}
return w
}

app.registerExtension({
name: "jovimetrix.help.tooltips",
async getCustomWidgets(app) {
return {
JTOOLTIP: (node, inputName, inputData, app) => ({
widget: node.addCustomWidget(JTooltipWidget(app, inputName, inputData[1]))
})
}
},
setup() {
const tooltipEl = $el("div.jov-graph-tooltip", {
parent: document.body,
});

let userTimeout = 750;
let tooltipTimeout;
const hideTooltip = () => {
if (tooltipTimeout) {
clearTimeout(tooltipTimeout);
}
tooltipEl.style.display = "none";
};
const showTooltip = (tooltip) => {
if (tooltipTimeout) {
clearTimeout(tooltipTimeout);
}
if (tooltip && userTimeout > 0) {
tooltipTimeout = setTimeout(() => {
tooltipEl.textContent = tooltip;
tooltipEl.style.display = "block";
tooltipEl.style.left = app.canvas.mouse[0] + "px";
tooltipEl.style.top = app.canvas.mouse[1] + "px";
const rect = tooltipEl.getBoundingClientRect();
if(rect.right > window.innerWidth) {
tooltipEl.style.left = (app.canvas.mouse[0] - rect.width) + "px";
}

if(rect.top < 0) {
tooltipEl.style.top = (app.canvas.mouse[1] + rect.height) + "px";
}
}, userTimeout);
}
};

const onCanvasPointerMove = function () {
hideTooltip();
const node = this.node_over;
if (!node) return;

if (node.flags.collapsed) return;

// jovian tooltip
const widget_tooltip = (node?.widgets || [])
.find(widget => widget.type === 'JTOOLTIP');
if (!widget_tooltip) return;
const tips = widget_tooltip.options.default || {};

// core tooltip
//const tooltips = node.constructor.nodeData?.tooltips;
//if (!tooltips) return;

const inputSlot = this.isOverNodeInput(node, this.graph_mouse[0], this.graph_mouse[1], [0, 0]);
if (inputSlot !== -1) {
let tip = tips?.[node.inputs[inputSlot].name];
return showTooltip(tip);
// return showTooltip(tooltips.input?.[node.inputs[inputSlot].name]);
}

const outputSlot = this.isOverNodeOutput(node, this.graph_mouse[0], this.graph_mouse[1], [0, 0]);
if (outputSlot !== -1) {
let tip = tips?.[node.outputs[outputSlot].name];
return showTooltip(tip);
// return showTooltip(tooltips.output?.[outputSlot]);
}

const widget = getHoveredWidget();
if (widget && !widget.element) {
console.info(widget)
let tip = tips?.[widget.name];
const def = widget.options.default;
if (def) {
tip += ` (default: ${def})`;
}
return showTooltip(tip);
//return showTooltip(tooltips.input?.[widget.name]);
}
}.bind(app.canvas);

app.ui.settings.addSetting({
id: "jovimetrix.cozy.tips",
name: "🇯 🎨 Tooltips Delay",
tooltip: "How long (in milliseconds) to wait before showing the tooltip. 0 will turn it off.",
type: "number",
defaultValue: 500,
attrs: {
min: 0,
step: 1,
},
onChange(value) {
if (value > 0) {
LiteGraph.pointerListenerAdd(app.canvasEl, "move", onCanvasPointerMove);
} else {
LiteGraph.pointerListenerRemove(app.canvasEl, "move", onCanvasPointerMove);
}
userTimeout = value;
},
});
},
});
Loading

0 comments on commit 7faa0d2

Please sign in to comment.