mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-09-12 12:37:01 +00:00
Add support for simple tooltips (#3842)
* Add support for simple tooltips * Fix overflow * Add tooltips for nodes in the default workflow * new line * Prevent potential crash * PR feedback * Hide tooltip when clicking (e.g. combo widget) * Refactor tooltips, add node level support * Fix * move * Fix test (and undo last change) * Fixed indent * Fix dom widgets, dont show tooltip if not over canvas
This commit is contained in:
122
web/extensions/core/tooltips.js
Normal file
122
web/extensions/core/tooltips.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { $el } from "../../scripts/ui.js";
|
||||
|
||||
// Adds support for tooltips
|
||||
|
||||
function getHoveredWidget() {
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = app.canvas.node_over;
|
||||
if (!node.widgets) return;
|
||||
|
||||
const graphPos = app.canvas.graph_mouse;
|
||||
|
||||
const x = graphPos[0] - node.pos[0];
|
||||
const y = graphPos[1] - node.pos[1];
|
||||
|
||||
for (const w of node.widgets) {
|
||||
let widgetWidth, widgetHeight;
|
||||
if (w.computeSize) {
|
||||
const sz = w.computeSize();
|
||||
widgetWidth = sz[0];
|
||||
widgetHeight = sz[1];
|
||||
} else {
|
||||
widgetWidth = w.width || node.size[0];
|
||||
widgetHeight = LiteGraph.NODE_WIDGET_HEIGHT;
|
||||
}
|
||||
|
||||
if (w.last_y !== undefined && x >= 6 && x <= widgetWidth - 12 && y >= w.last_y && y <= w.last_y + widgetHeight) {
|
||||
return w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.Tooltips",
|
||||
setup() {
|
||||
const tooltipEl = $el("div.comfy-graph-tooltip", {
|
||||
parent: document.body,
|
||||
});
|
||||
let idleTimeout;
|
||||
|
||||
const hideTooltip = () => {
|
||||
tooltipEl.style.display = "none";
|
||||
};
|
||||
const showTooltip = (tooltip) => {
|
||||
if (!tooltip) return;
|
||||
|
||||
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";
|
||||
}
|
||||
};
|
||||
const getInputTooltip = (nodeData, name) => {
|
||||
const inputDef = nodeData.input?.required?.[name] ?? nodeData.input?.optional?.[name];
|
||||
return inputDef?.[1]?.tooltip;
|
||||
};
|
||||
const onIdle = () => {
|
||||
const { canvas } = app;
|
||||
const node = canvas.node_over;
|
||||
if (!node) return;
|
||||
|
||||
const nodeData = node.constructor.nodeData ?? {};
|
||||
|
||||
if (node.constructor.title_mode !== LiteGraph.NO_TITLE && canvas.graph_mouse[1] < node.pos[1]) {
|
||||
return showTooltip(nodeData.description);
|
||||
}
|
||||
|
||||
if (node.flags?.collapsed) return;
|
||||
|
||||
const inputSlot = canvas.isOverNodeInput(node, canvas.graph_mouse[0], canvas.graph_mouse[1], [0, 0]);
|
||||
if (inputSlot !== -1) {
|
||||
const inputName = node.inputs[inputSlot].name;
|
||||
return showTooltip(getInputTooltip(nodeData, inputName));
|
||||
}
|
||||
|
||||
const outputSlot = canvas.isOverNodeOutput(node, canvas.graph_mouse[0], canvas.graph_mouse[1], [0, 0]);
|
||||
if (outputSlot !== -1) {
|
||||
return showTooltip(nodeData.output_tooltips?.[outputSlot]);
|
||||
}
|
||||
|
||||
const widget = getHoveredWidget();
|
||||
// Dont show for DOM widgets, these use native browser tooltips as we dont get proper mouse events on these
|
||||
if (widget && !widget.element) {
|
||||
return showTooltip(widget.tooltip ?? getInputTooltip(nodeData, widget.name));
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseMove = (e) => {
|
||||
hideTooltip();
|
||||
clearTimeout(idleTimeout);
|
||||
|
||||
if(e.target.nodeName !== "CANVAS") return
|
||||
idleTimeout = setTimeout(onIdle, 500);
|
||||
};
|
||||
|
||||
app.ui.settings.addSetting({
|
||||
id: "Comfy.EnableTooltips",
|
||||
name: "Enable Tooltips",
|
||||
type: "boolean",
|
||||
defaultValue: true,
|
||||
onChange(value) {
|
||||
if (value) {
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
window.addEventListener("click", hideTooltip);
|
||||
} else {
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
window.removeEventListener("click", hideTooltip);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@@ -181,7 +181,7 @@ export function mergeIfValid(output, config2, forceUpdate, recreateWidget, confi
|
||||
|
||||
const isNumber = config1[0] === "INT" || config1[0] === "FLOAT";
|
||||
for (const k of keys.values()) {
|
||||
if (k !== "default" && k !== "forceInput" && k !== "defaultInput" && k !== "control_after_generate" && k !== "multiline") {
|
||||
if (k !== "default" && k !== "forceInput" && k !== "defaultInput" && k !== "control_after_generate" && k !== "multiline" && k !== "tooltip") {
|
||||
let v1 = config1[1][k];
|
||||
let v2 = config2[1]?.[k];
|
||||
|
||||
|
@@ -1713,9 +1713,10 @@ export class ComfyApp {
|
||||
for (const o in nodeData["output"]) {
|
||||
let output = nodeData["output"][o];
|
||||
if(output instanceof Array) output = "COMBO";
|
||||
const outputTooltip = nodeData["output_tooltips"]?.[o];
|
||||
const outputName = nodeData["output_name"][o] || output;
|
||||
const outputShape = nodeData["output_is_list"][o] ? LiteGraph.GRID_SHAPE : LiteGraph.CIRCLE_SHAPE ;
|
||||
this.addOutput(outputName, output, { shape: outputShape });
|
||||
this.addOutput(outputName, output, { shape: outputShape, tooltip: outputTooltip });
|
||||
}
|
||||
|
||||
const s = this.computeSize();
|
||||
|
@@ -223,6 +223,12 @@ LGraphNode.prototype.addDOMWidget = function (name, type, element, options) {
|
||||
document.addEventListener("mousedown", mouseDownHandler);
|
||||
}
|
||||
|
||||
const { nodeData } = this.constructor;
|
||||
const tooltip = (nodeData?.input.required?.[name] ?? nodeData?.input.optional?.[name])?.[1]?.tooltip;
|
||||
if (tooltip && !element.title) {
|
||||
element.title = tooltip;
|
||||
}
|
||||
|
||||
const widget = {
|
||||
type,
|
||||
name,
|
||||
|
@@ -75,6 +75,7 @@ export function addValueControlWidgets(node, targetWidget, defaultValue = "rando
|
||||
serialize: false, // Don't include this in prompt.
|
||||
}
|
||||
);
|
||||
valueControl.tooltip = "Allows the linked widget to be changed automatically, for example randomizing the noise seed.";
|
||||
valueControl[IS_CONTROL_WIDGET] = true;
|
||||
updateControlWidgetLabel(valueControl);
|
||||
widgets.push(valueControl);
|
||||
@@ -95,6 +96,7 @@ export function addValueControlWidgets(node, targetWidget, defaultValue = "rando
|
||||
}
|
||||
);
|
||||
updateControlWidgetLabel(comboFilter);
|
||||
comboFilter.tooltip = "Allows for filtering the list of values when changing the value via the control generate mode. Allows for RegEx matches in the format /abc/ to only filter to values containing 'abc'."
|
||||
|
||||
widgets.push(comboFilter);
|
||||
}
|
||||
|
@@ -645,3 +645,20 @@ dialog::backdrop {
|
||||
audio.comfy-audio.empty-audio-widget {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comfy-graph-tooltip {
|
||||
background: var(--comfy-input-bg);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
|
||||
color: var(--input-text);
|
||||
display: none;
|
||||
font-family: sans-serif;
|
||||
left: 0;
|
||||
max-width: 30vw;
|
||||
padding: 4px 8px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: translate(5px, calc(-100% - 5px));
|
||||
white-space: pre-wrap;
|
||||
z-index: 99999;
|
||||
}
|
||||
|
Reference in New Issue
Block a user