Files

315 lines
8.8 KiB
JavaScript

import { COLORS } from "./constants.js";
import { createPresetManager } from "./preset_manager.js";
import {
drawGroupBorder,
drawWidgetOutline,
findWidget,
hookWidget,
registerNode,
setInputDotColor,
waitForWidgets,
} from "./utils.js";
import { replaceWithInlineTextToggle } from "./widgets/inline_text_toggle_widget.js";
// --- Constants ---
const NODE_NAME = "CharacterBuilder";
// Override pairs: text widget name -> mode widget name
const OVERRIDE_FIELDS = [
"eyes_override",
"eyewear_override",
"face_details_override",
"face_piercings_override",
"nail_color_override",
"facewear_override",
"neckwear_override",
"armwear_override",
"upper_details_override",
"mid_details_override",
"lower_details_override",
];
const BOOL_FIELDS = [
"show_eyes",
"show_eyeballs",
"bald",
"show_nails",
"show_makeup",
"toggle_accessories",
"show_body",
"show_body_details",
"show_upper_body",
"show_mid_body",
"show_lower_body",
"show_butt",
...OVERRIDE_FIELDS.map((f) => `${f}_mode`),
];
// --- Replace native widget pairs with InlineTextToggleWidget ---
const replaceWithToggleWidgets = (node) => {
const toggleWidgets = {};
for (const field of OVERRIDE_FIELDS) {
const textWidget = findWidget(node, field);
const modeWidget = findWidget(node, `${field}_mode`);
if (!textWidget || !modeWidget) continue;
toggleWidgets[field] = replaceWithInlineTextToggle(
node,
textWidget,
modeWidget,
);
}
return toggleWidgets;
};
// --- Capture / Recall ---
const captureState = (node) => {
const state = {};
for (const field of BOOL_FIELDS) {
const w = findWidget(node, field);
if (w) state[field] = w.value ?? false;
}
for (const field of OVERRIDE_FIELDS) {
const w = findWidget(node, field);
if (w) state[field] = w.value ?? "";
}
return state;
};
const recallState = (node, state, toggleWidgets) => {
for (const field of [...BOOL_FIELDS, ...OVERRIDE_FIELDS]) {
if (!(field in state)) continue;
const w = findWidget(node, field);
if (!w) continue;
w.value = state[field];
w.callback?.(state[field]);
}
applyAllVisibility(node, toggleWidgets);
if (node.graph) node.graph.setDirtyCanvas(true, true);
};
// --- Visibility cascade ---
const setDisabled = (node, fields, disabled) => {
for (const field of fields) {
const w = findWidget(node, field);
if (w) w.disabled = disabled;
}
};
const setToggleWidgetDisabled = (node, toggleWidgets, fields, disabled) => {
for (const field of fields) {
const helper = toggleWidgets[field];
if (helper) helper.disabled = disabled;
}
};
const setDotsDisabled = (node, fields, disabled) => {
for (const field of fields) {
setInputDotColor(node, field, !disabled);
}
};
const applyAllVisibility = (node, toggleWidgets) => {
const get = (name) => findWidget(node, name)?.value ?? true;
const showEyes = get("show_eyes");
const showEyeballs = get("show_eyeballs");
const isBald = get("bald");
const showNails = get("show_nails");
const showMakeup = get("show_makeup");
const toggleAccessories = get("toggle_accessories");
const showBody = get("show_body");
const showBodyDetails = get("show_body_details");
const showUpperBody = get("show_upper_body");
const showMidBody = get("show_mid_body");
const showLowerBody = get("show_lower_body");
const showButt = get("show_butt");
// show_eyes -> show_eyeballs + eye_details dot
setDisabled(node, ["show_eyeballs"], !showEyes);
setDotsDisabled(node, ["eye_details"], !showEyes);
// show_eyeballs -> eyes_override + raw dots
setToggleWidgetDisabled(
node,
toggleWidgets,
["eyes_override"],
!(showEyes && showEyeballs),
);
setDotsDisabled(
node,
["eye_color", "eye_type", "pupils"],
!(showEyes && showEyeballs),
);
// bald -> hair_color + hair_style dots
setDotsDisabled(node, ["hair_color", "hair_style"], isBald);
// show_nails -> nail_color_override toggle widget + nail_type/nail_color dots
setDotsDisabled(node, ["nail_type"], !showNails);
setDotsDisabled(node, ["nail_color"], !(showNails && showMakeup));
// show_makeup -> makeup + makeup_modifiers dots; nail_color_override also gated by show_makeup
setDotsDisabled(node, ["makeup", "makeup_modifiers"], !showMakeup);
setToggleWidgetDisabled(
node,
toggleWidgets,
["nail_color_override"],
!(showNails && showMakeup),
);
// toggle_accessories -> 3 accessory override toggle widgets + raw dots
setToggleWidgetDisabled(
node,
toggleWidgets,
["facewear_override", "neckwear_override", "armwear_override"],
!toggleAccessories,
);
setDotsDisabled(
node,
["facewear", "neckwear", "armwear"],
!toggleAccessories,
);
// show_body_details -> 3 body details override toggle widgets + raw dots
setToggleWidgetDisabled(
node,
toggleWidgets,
["upper_details_override"],
!(showBody && showUpperBody && showBodyDetails),
);
setDotsDisabled(
node,
["upper_details"],
!(showBody && showUpperBody && showBodyDetails),
);
setToggleWidgetDisabled(
node,
toggleWidgets,
["mid_details_override"],
!(showBody && showMidBody && showBodyDetails),
);
setDotsDisabled(
node,
["mid_details"],
!(showBody && showMidBody && showBodyDetails),
);
setToggleWidgetDisabled(
node,
toggleWidgets,
["lower_details_override"],
!(showBody && showLowerBody && showBodyDetails),
);
setDotsDisabled(
node,
["lower_details"],
!(showBody && showLowerBody && showBodyDetails),
);
// show_body -> 4 body sub-toggles
setDisabled(
node,
["show_upper_body", "show_mid_body", "show_lower_body", "show_butt"],
!showBody,
);
setDotsDisabled(node, ["upper_body"], !(showBody && showUpperBody));
setDotsDisabled(node, ["mid_body"], !(showBody && showMidBody));
setDotsDisabled(node, ["lower_body"], !(showBody && showLowerBody));
setDotsDisabled(node, ["butt"], !(showBody && showButt));
if (node.graph) node.graph.setDirtyCanvas(true, true);
};
// --- Attach ---
const attachCharacterBuilder = (node) => {
// --- Replace override pairs with InlineTextToggleWidget ---
const toggleWidgets = replaceWithToggleWidgets(node);
// --- Hook all toggle bools to re-apply visibility cascade ---
const toggleNames = [
"show_eyes",
"show_eyeballs",
"bald",
"show_nails",
"show_makeup",
"toggle_accessories",
"show_body",
"show_body_details",
"show_upper_body",
"show_mid_body",
"show_lower_body",
"show_butt",
];
for (const name of toggleNames) {
const w = findWidget(node, name);
if (w) hookWidget(w, () => applyAllVisibility(node, toggleWidgets));
}
// Initial visibility
applyAllVisibility(node, toggleWidgets);
// --- Toggle borders ---
const outlines = [
{ name: "show_eyeballs" },
{ name: "show_makeup" },
{ name: "show_upper_body" },
{ name: "show_mid_body" },
{ name: "show_lower_body" },
{ name: "show_butt" },
];
const groups = [
{ from: "show_eyes", to: "eyewear_override" },
{ from: "bald", to: "bald" },
{ from: "show_nails", to: "show_makeup" },
{ from: "toggle_accessories", to: "armwear_override" },
{ from: "show_body", to: "show_butt" },
];
const original = node.onDrawForeground;
node.onDrawForeground = function (ctx) {
if (original) original.call(this, ctx);
for (const widget of outlines) {
drawWidgetOutline(
ctx,
node,
widget.name,
COLORS.orange,
COLORS.blue,
);
}
for (const group of groups) {
drawGroupBorder(
ctx,
node,
group.from,
group.to,
COLORS.toggle_on,
COLORS.toggle_off,
);
}
};
// --- Preset manager ---
const { presetRow, exportImportRow } = createPresetManager(node, {
nodeLabel: NODE_NAME,
onCapture: () => captureState(node),
onRecall: (state) => recallState(node, state, toggleWidgets),
});
node.widgets.push(presetRow);
node.widgets.push(exportImportRow);
};
// --- Registration ---
registerNode(NODE_NAME, (node) => waitForWidgets(node, attachCharacterBuilder));