| import { app } from "scripts/app.js";
|
| import type {
|
| ContextMenuItem,
|
| LGraphNode as TLGraphNode,
|
| IWidget,
|
| LGraphCanvas,
|
| SerializedLGraphNode,
|
| Vector2,
|
| AdjustedMouseEvent,
|
| } from "typings/litegraph.js";
|
| import type { ComfyObjectInfo, ComfyNodeConstructor } from "typings/comfy.js";
|
| import { RgthreeBaseServerNode } from "./base_node.js";
|
| import { rgthree } from "./rgthree.js";
|
| import { addConnectionLayoutSupport } from "./utils.js";
|
| import { NodeTypesString } from "./constants.js";
|
| import {
|
| drawInfoIcon,
|
| drawNumberWidgetPart,
|
| drawRoundedRectangle,
|
| drawTogglePart,
|
| fitString,
|
| isLowQuality,
|
| } from "./utils_canvas.js";
|
| import {
|
| RgthreeBaseHitAreas,
|
| RgthreeBaseWidget,
|
| RgthreeBetterButtonWidget,
|
| RgthreeDividerWidget,
|
| } from "./utils_widgets.js";
|
| import { rgthreeApi } from "rgthree/common/rgthree_api.js";
|
| import { showLoraChooser } from "./utils_menu.js";
|
| import { moveArrayItem, removeArrayItem } from "rgthree/common/shared_utils.js";
|
| import { RgthreeLoraInfoDialog } from "./dialog_info.js";
|
| import type { RgthreeModelInfo } from "typings/rgthree.js";
|
| import { LORA_INFO_SERVICE } from "rgthree/common/model_info_service.js";
|
|
|
|
|
| const PROP_LABEL_SHOW_STRENGTHS = "Show Strengths";
|
| const PROP_LABEL_SHOW_STRENGTHS_STATIC = `@${PROP_LABEL_SHOW_STRENGTHS}`;
|
| const PROP_VALUE_SHOW_STRENGTHS_SINGLE = "Single Strength";
|
| const PROP_VALUE_SHOW_STRENGTHS_SEPARATE = "Separate Model & Clip";
|
|
|
| |
| |
| |
|
|
| class RgthreePowerLoraLoader extends RgthreeBaseServerNode {
|
| static override title = NodeTypesString.POWER_LORA_LOADER;
|
| static override type = NodeTypesString.POWER_LORA_LOADER;
|
| static comfyClass = NodeTypesString.POWER_LORA_LOADER;
|
|
|
| override serialize_widgets = true;
|
|
|
| private logger = rgthree.newLogSession(`[Power Lora Stack]`);
|
|
|
| static [PROP_LABEL_SHOW_STRENGTHS_STATIC] = {
|
| type: "combo",
|
| values: [PROP_VALUE_SHOW_STRENGTHS_SINGLE, PROP_VALUE_SHOW_STRENGTHS_SEPARATE],
|
| };
|
|
|
|
|
| private loraWidgetsCounter = 0;
|
|
|
|
|
| private widgetButtonSpacer: IWidget | null = null;
|
|
|
| constructor(title = NODE_CLASS.title) {
|
| super(title);
|
|
|
| this.properties[PROP_LABEL_SHOW_STRENGTHS] = PROP_VALUE_SHOW_STRENGTHS_SINGLE;
|
|
|
|
|
| rgthreeApi.getLoras();
|
| }
|
|
|
| |
| |
| |
| |
|
|
| override configure(info: SerializedLGraphNode<TLGraphNode>): void {
|
| while (this.widgets?.length) this.removeWidget(0);
|
| this.widgetButtonSpacer = null;
|
| super.configure(info);
|
|
|
| (this as any)._tempWidth = this.size[0];
|
| (this as any)._tempHeight = this.size[1];
|
| for (const widgetValue of info.widgets_values || []) {
|
| if (widgetValue?.lora !== undefined) {
|
| const widget = this.addNewLoraWidget();
|
| widget.value = { ...widgetValue };
|
| }
|
| }
|
| this.addNonLoraWidgets();
|
| this.size[0] = (this as any)._tempWidth;
|
| this.size[1] = Math.max((this as any)._tempHeight, this.computeSize()[1]);
|
| }
|
|
|
| |
| |
| |
|
|
| override onNodeCreated() {
|
| super.onNodeCreated?.();
|
| this.addNonLoraWidgets();
|
| const computed = this.computeSize();
|
| this.size = this.size || [0, 0];
|
| this.size[0] = Math.max(this.size[0], computed[0]);
|
| this.size[1] = Math.max(this.size[1], computed[1]);
|
| this.setDirtyCanvas(true, true);
|
| }
|
|
|
|
|
| private addNewLoraWidget(lora?: string) {
|
| this.loraWidgetsCounter++;
|
| const widget = this.addCustomWidget(
|
| new PowerLoraLoaderWidget("lora_" + this.loraWidgetsCounter),
|
| );
|
| if (lora) widget.setLora(lora);
|
| if (this.widgetButtonSpacer) {
|
| moveArrayItem(this.widgets, widget, this.widgets.indexOf(this.widgetButtonSpacer));
|
| }
|
| return widget;
|
| }
|
|
|
|
|
| private addNonLoraWidgets() {
|
| moveArrayItem(
|
| this.widgets,
|
| this.addCustomWidget(
|
| new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }),
|
| ),
|
| 0,
|
| );
|
| moveArrayItem(this.widgets, this.addCustomWidget(new PowerLoraLoaderHeaderWidget()), 1);
|
|
|
| this.widgetButtonSpacer = this.addCustomWidget(
|
| new RgthreeDividerWidget({ marginTop: 4, marginBottom: 0, thickness: 0 }),
|
| );
|
|
|
| this.addCustomWidget(
|
| new RgthreeBetterButtonWidget(
|
| "➕ Add Lora",
|
| (event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) => {
|
| rgthreeApi.getLoras().then((loras) => {
|
| showLoraChooser(
|
| event as PointerEvent,
|
| (value: ContextMenuItem | string) => {
|
| if (typeof value === "string") {
|
| if (value.includes("Power Lora Chooser")) {
|
|
|
| } else if (value !== "NONE") {
|
| this.addNewLoraWidget(value);
|
| const computed = this.computeSize();
|
| const tempHeight = (this as any)._tempHeight ?? 15;
|
| this.size[1] = Math.max(tempHeight, computed[1]);
|
| this.setDirtyCanvas(true, true);
|
| }
|
| }
|
|
|
| },
|
| null,
|
| [...loras],
|
| );
|
| });
|
| return true;
|
| },
|
| ),
|
| );
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| override getSlotInPosition(canvasX: number, canvasY: number): any {
|
| const slot = super.getSlotInPosition(canvasX, canvasY);
|
|
|
| if (!slot) {
|
| let lastWidget = null;
|
| for (const widget of this.widgets) {
|
|
|
| if (!widget.last_y) return;
|
| if (canvasY > this.pos[1] + widget.last_y) {
|
| lastWidget = widget;
|
| continue;
|
| }
|
| break;
|
| }
|
|
|
| if (lastWidget?.name?.startsWith("lora_")) {
|
| return { widget: lastWidget, output: { type: "LORA WIDGET" } };
|
| }
|
| }
|
| return slot;
|
| }
|
|
|
| |
| |
| |
|
|
| override getSlotMenuOptions(slot: any): ContextMenuItem[] | null {
|
|
|
|
|
|
|
| if (slot?.widget?.name?.startsWith("lora_")) {
|
| const widget = slot.widget as PowerLoraLoaderWidget;
|
| const index = this.widgets.indexOf(widget);
|
| const canMoveUp = !!this.widgets[index - 1]?.name?.startsWith("lora_");
|
| const canMoveDown = !!this.widgets[index + 1]?.name?.startsWith("lora_");
|
| const menuItems: ContextMenuItem[] = [
|
| {
|
| content: `ℹ️ Show Info`,
|
| callback: () => {
|
| widget.showLoraInfoDialog();
|
| },
|
| },
|
| null,
|
| {
|
| content: `${widget.value.on ? "⚫" : "🟢"} Toggle ${widget.value.on ? "Off" : "On"}`,
|
| callback: () => {
|
| widget.value.on = !widget.value.on;
|
| },
|
| },
|
| {
|
| content: `⬆️ Move Up`,
|
| disabled: !canMoveUp,
|
| callback: () => {
|
| moveArrayItem(this.widgets, widget, index - 1);
|
| },
|
| },
|
| {
|
| content: `⬇️ Move Down`,
|
| disabled: !canMoveDown,
|
| callback: () => {
|
| moveArrayItem(this.widgets, widget, index + 1);
|
| },
|
| },
|
| {
|
| content: `🗑️ Remove`,
|
| callback: () => {
|
| removeArrayItem(this.widgets, widget);
|
| },
|
| },
|
| ];
|
|
|
| let canvas = app.canvas as LGraphCanvas;
|
| new LiteGraph.ContextMenu(
|
| menuItems,
|
| { title: "LORA WIDGET", event: rgthree.lastAdjustedMouseEvent! },
|
| canvas.getCanvasWindow(),
|
| );
|
|
|
| return null;
|
| }
|
| return this.defaultGetSlotMenuOptions(slot);
|
| }
|
|
|
| |
| |
|
|
| refreshComboInNode(defs: any) {
|
| rgthreeApi.getLoras(true);
|
| }
|
|
|
| |
| |
|
|
| hasLoraWidgets() {
|
| return !!this.widgets?.find((w) => w.name?.startsWith("lora_"));
|
| }
|
|
|
| |
| |
| |
|
|
| allLorasState() {
|
| let allOn = true;
|
| let allOff = true;
|
| for (const widget of this.widgets) {
|
| if (widget.name?.startsWith("lora_")) {
|
| const on = widget.value?.on;
|
| allOn = allOn && on === true;
|
| allOff = allOff && on === false;
|
| if (!allOn && !allOff) {
|
| return null;
|
| }
|
| }
|
| }
|
| return allOn && this.widgets?.length ? true : false;
|
| }
|
|
|
| |
| |
|
|
| toggleAllLoras() {
|
| const allOn = this.allLorasState();
|
| const toggledTo = !allOn ? true : false;
|
| for (const widget of this.widgets) {
|
| if (widget.name?.startsWith("lora_")) {
|
| widget.value.on = toggledTo;
|
| }
|
| }
|
| }
|
|
|
| static override setUp(comfyClass: ComfyNodeConstructor, nodeData: ComfyObjectInfo) {
|
| RgthreeBaseServerNode.registerForOverride(comfyClass, nodeData, NODE_CLASS);
|
| }
|
|
|
| static override onRegisteredForOverride(comfyClass: any, ctxClass: any) {
|
| addConnectionLayoutSupport(NODE_CLASS, app, [
|
| ["Left", "Right"],
|
| ["Right", "Left"],
|
| ]);
|
| setTimeout(() => {
|
| NODE_CLASS.category = comfyClass.category;
|
| });
|
| }
|
|
|
| override getHelp() {
|
| return `
|
| <p>
|
| The ${this.type!.replace("(rgthree)", "")} is a powerful node that condenses 100s of pixels
|
| of functionality in a single, dynamic node that allows you to add loras, change strengths,
|
| and quickly toggle on/off all without taking up half your screen.
|
| </p>
|
| <ul>
|
| <li><p>
|
| Add as many Lora's as you would like by clicking the "+ Add Lora" button.
|
| There's no real limit!
|
| </p></li>
|
| <li><p>
|
| Right-click on a Lora widget for special options to move the lora up or down
|
| (no image affect, only presentational), toggle it on/off, or delete the row all together.
|
| </p></li>
|
| <li>
|
| <p>
|
| <strong>Properties.</strong> You can change the following properties (by right-clicking
|
| on the node, and select "Properties" or "Properties Panel" from the menu):
|
| </p>
|
| <ul>
|
| <li><p>
|
| <code>${PROP_LABEL_SHOW_STRENGTHS}</code> - Change between showing a single, simple
|
| strength (which will be used for both model and clip), or a more advanced view with
|
| both model and clip strengths being modifiable.
|
| </p></li>
|
| </ul>
|
| </li>
|
| </ul>`;
|
| }
|
| }
|
|
|
| |
| |
| |
|
|
| class PowerLoraLoaderHeaderWidget extends RgthreeBaseWidget<{ type: string }> {
|
| private showModelAndClip: boolean | null = null;
|
|
|
| value = { type: "PowerLoraLoaderHeaderWidget" };
|
|
|
| protected override hitAreas: RgthreeBaseHitAreas<"toggle"> = {
|
| toggle: { bounds: [0, 0] as Vector2, onDown: this.onToggleDown },
|
| };
|
|
|
| constructor(name: string = "PowerLoraLoaderHeaderWidget") {
|
| super(name);
|
| }
|
|
|
| draw(
|
| ctx: CanvasRenderingContext2D,
|
| node: RgthreePowerLoraLoader,
|
| w: number,
|
| posY: number,
|
| height: number,
|
| ) {
|
| if (!node.hasLoraWidgets()) {
|
| return;
|
| }
|
|
|
|
|
| this.showModelAndClip =
|
| node.properties[PROP_LABEL_SHOW_STRENGTHS] === PROP_VALUE_SHOW_STRENGTHS_SEPARATE;
|
| const margin = 10;
|
| const innerMargin = margin * 0.33;
|
| const lowQuality = isLowQuality();
|
| const allLoraState = node.allLorasState();
|
|
|
|
|
| posY += 2;
|
| const midY = posY + height * 0.5;
|
| let posX = 10;
|
| ctx.save();
|
| this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: allLoraState });
|
|
|
| if (!lowQuality) {
|
| posX += this.hitAreas.toggle.bounds[1] + innerMargin;
|
|
|
| ctx.globalAlpha = app.canvas.editor_alpha * 0.55;
|
| ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
|
| ctx.textAlign = "left";
|
| ctx.textBaseline = "middle";
|
| ctx.fillText("Toggle All", posX, midY);
|
|
|
| let rposX = node.size[0] - margin - innerMargin - innerMargin;
|
| ctx.textAlign = "center";
|
| ctx.fillText(
|
| this.showModelAndClip ? "Clip" : "Strength",
|
| rposX - drawNumberWidgetPart.WIDTH_TOTAL / 2,
|
| midY,
|
| );
|
| if (this.showModelAndClip) {
|
| rposX = rposX - drawNumberWidgetPart.WIDTH_TOTAL - innerMargin * 2;
|
| ctx.fillText("Model", rposX - drawNumberWidgetPart.WIDTH_TOTAL / 2, midY);
|
| }
|
| }
|
| ctx.restore();
|
| }
|
|
|
| |
| |
|
|
| onToggleDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| (node as RgthreePowerLoraLoader).toggleAllLoras();
|
| this.cancelMouseDown();
|
| return true;
|
| }
|
| }
|
|
|
| const DEFAULT_LORA_WIDGET_DATA: PowerLoraLoaderWidgetValue = {
|
| on: true,
|
| lora: null as string | null,
|
| strength: 1,
|
| strengthTwo: null as number | null,
|
| };
|
|
|
| type PowerLoraLoaderWidgetValue = {
|
| on: boolean;
|
| lora: string | null;
|
| strength: number;
|
| strengthTwo: number | null;
|
| };
|
|
|
| |
| |
|
|
| class PowerLoraLoaderWidget extends RgthreeBaseWidget<PowerLoraLoaderWidgetValue> {
|
|
|
| private haveMouseMovedStrength = false;
|
| private loraInfoPromise: Promise<RgthreeModelInfo | null> | null = null;
|
| private loraInfo: RgthreeModelInfo | null = null;
|
|
|
| private showModelAndClip: boolean | null = null;
|
|
|
| protected override hitAreas: RgthreeBaseHitAreas<
|
| | "toggle"
|
| | "lora"
|
|
|
| | "strengthDec"
|
| | "strengthVal"
|
| | "strengthInc"
|
| | "strengthAny"
|
| | "strengthTwoDec"
|
| | "strengthTwoVal"
|
| | "strengthTwoInc"
|
| | "strengthTwoAny"
|
| > = {
|
| toggle: { bounds: [0, 0] as Vector2, onDown: this.onToggleDown },
|
| lora: { bounds: [0, 0] as Vector2, onDown: this.onLoraDown },
|
|
|
|
|
| strengthDec: { bounds: [0, 0] as Vector2, onDown: this.onStrengthDecDown },
|
| strengthVal: { bounds: [0, 0] as Vector2, onUp: this.onStrengthValUp },
|
| strengthInc: { bounds: [0, 0] as Vector2, onDown: this.onStrengthIncDown },
|
| strengthAny: { bounds: [0, 0] as Vector2, onMove: this.onStrengthAnyMove },
|
|
|
| strengthTwoDec: { bounds: [0, 0] as Vector2, onDown: this.onStrengthTwoDecDown },
|
| strengthTwoVal: { bounds: [0, 0] as Vector2, onUp: this.onStrengthTwoValUp },
|
| strengthTwoInc: { bounds: [0, 0] as Vector2, onDown: this.onStrengthTwoIncDown },
|
| strengthTwoAny: { bounds: [0, 0] as Vector2, onMove: this.onStrengthTwoAnyMove },
|
| };
|
|
|
| constructor(name: string) {
|
| super(name);
|
| }
|
|
|
| private _value = {
|
| on: true,
|
| lora: null as string | null,
|
| strength: 1,
|
| strengthTwo: null as number | null,
|
| };
|
|
|
| set value(v) {
|
| this._value = v;
|
|
|
| if (typeof this._value !== "object") {
|
| this._value = { ...DEFAULT_LORA_WIDGET_DATA };
|
| if (this.showModelAndClip) {
|
| this._value.strengthTwo = this._value.strength;
|
| }
|
| }
|
| this.getLoraInfo();
|
| }
|
|
|
| get value() {
|
| return this._value;
|
| }
|
|
|
| setLora(lora: string) {
|
| this._value.lora = lora;
|
| this.getLoraInfo();
|
| }
|
|
|
|
|
| draw(ctx: CanvasRenderingContext2D, node: TLGraphNode, w: number, posY: number, height: number) {
|
|
|
|
|
| let currentShowModelAndClip =
|
| node.properties[PROP_LABEL_SHOW_STRENGTHS] === PROP_VALUE_SHOW_STRENGTHS_SEPARATE;
|
| if (this.showModelAndClip !== currentShowModelAndClip) {
|
| let oldShowModelAndClip = this.showModelAndClip;
|
| this.showModelAndClip = currentShowModelAndClip;
|
| if (this.showModelAndClip) {
|
|
|
| if (oldShowModelAndClip != null) {
|
| this.value.strengthTwo = this.value.strength ?? 1;
|
| }
|
| } else {
|
| this.value.strengthTwo = null;
|
| this.hitAreas.strengthTwoDec.bounds = [0, -1];
|
| this.hitAreas.strengthTwoVal.bounds = [0, -1];
|
| this.hitAreas.strengthTwoInc.bounds = [0, -1];
|
| this.hitAreas.strengthTwoAny.bounds = [0, -1];
|
| }
|
| }
|
|
|
| ctx.save();
|
| const margin = 10;
|
| const innerMargin = margin * 0.33;
|
| const lowQuality = isLowQuality();
|
| const midY = posY + height * 0.5;
|
|
|
|
|
| let posX = margin;
|
|
|
|
|
| drawRoundedRectangle(ctx, { posX, posY, height, width: node.size[0] - margin * 2 });
|
|
|
|
|
| this.hitAreas.toggle.bounds = drawTogglePart(ctx, { posX, posY, height, value: this.value.on });
|
| posX += this.hitAreas.toggle.bounds[1] + innerMargin;
|
|
|
|
|
| if (lowQuality) {
|
| ctx.restore();
|
| return;
|
| }
|
|
|
|
|
| if (!this.value.on) {
|
| ctx.globalAlpha = app.canvas.editor_alpha * 0.4;
|
| }
|
|
|
| ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
|
|
|
|
|
|
|
| let rposX = node.size[0] - margin - innerMargin - innerMargin;
|
|
|
| const strengthValue = this.showModelAndClip
|
| ? this.value.strengthTwo ?? 1
|
| : this.value.strength ?? 1;
|
|
|
| let textColor: string | undefined = undefined;
|
| if (this.loraInfo?.strengthMax != null && strengthValue > this.loraInfo?.strengthMax) {
|
| textColor = "#c66";
|
| } else if (this.loraInfo?.strengthMin != null && strengthValue < this.loraInfo?.strengthMin) {
|
| textColor = "#c66";
|
| }
|
|
|
| const [leftArrow, text, rightArrow] = drawNumberWidgetPart(ctx, {
|
| posX: node.size[0] - margin - innerMargin - innerMargin,
|
| posY,
|
| height,
|
| value: strengthValue,
|
| direction: -1,
|
| textColor,
|
| });
|
|
|
| this.hitAreas.strengthDec.bounds = leftArrow;
|
| this.hitAreas.strengthVal.bounds = text;
|
| this.hitAreas.strengthInc.bounds = rightArrow;
|
| this.hitAreas.strengthAny.bounds = [leftArrow[0], rightArrow[0] + rightArrow[1] - leftArrow[0]];
|
|
|
| rposX = leftArrow[0] - innerMargin;
|
|
|
| if (this.showModelAndClip) {
|
| rposX -= innerMargin;
|
|
|
|
|
| this.hitAreas.strengthTwoDec.bounds = this.hitAreas.strengthDec.bounds;
|
| this.hitAreas.strengthTwoVal.bounds = this.hitAreas.strengthVal.bounds;
|
| this.hitAreas.strengthTwoInc.bounds = this.hitAreas.strengthInc.bounds;
|
| this.hitAreas.strengthTwoAny.bounds = this.hitAreas.strengthAny.bounds;
|
|
|
| let textColor: string | undefined = undefined;
|
| if (this.loraInfo?.strengthMax != null && this.value.strength > this.loraInfo?.strengthMax) {
|
| textColor = "#c66";
|
| } else if (
|
| this.loraInfo?.strengthMin != null &&
|
| this.value.strength < this.loraInfo?.strengthMin
|
| ) {
|
| textColor = "#c66";
|
| }
|
| const [leftArrow, text, rightArrow] = drawNumberWidgetPart(ctx, {
|
| posX: rposX,
|
| posY,
|
| height,
|
| value: this.value.strength ?? 1,
|
| direction: -1,
|
| textColor,
|
| });
|
| this.hitAreas.strengthDec.bounds = leftArrow;
|
| this.hitAreas.strengthVal.bounds = text;
|
| this.hitAreas.strengthInc.bounds = rightArrow;
|
| this.hitAreas.strengthAny.bounds = [
|
| leftArrow[0],
|
| rightArrow[0] + rightArrow[1] - leftArrow[0],
|
| ];
|
| rposX = leftArrow[0] - innerMargin;
|
| }
|
|
|
| const infoIconSize = height * 0.66;
|
| const infoWidth = infoIconSize + innerMargin + innerMargin;
|
|
|
| if ((this.hitAreas as any)["info"]) {
|
| rposX -= innerMargin;
|
| drawInfoIcon(ctx, rposX - infoIconSize, posY + (height - infoIconSize) / 2, infoIconSize);
|
|
|
| (this.hitAreas as any).info.bounds = [rposX - infoIconSize, infoWidth];
|
| rposX = rposX - infoIconSize - innerMargin;
|
| }
|
|
|
|
|
| const loraWidth = rposX - posX;
|
| ctx.textAlign = "left";
|
| ctx.textBaseline = "middle";
|
| const loraLabel = String(this.value?.lora || "None");
|
| ctx.fillText(fitString(ctx, loraLabel, loraWidth), posX, midY);
|
|
|
| this.hitAreas.lora.bounds = [posX, loraWidth];
|
| posX += loraWidth + innerMargin;
|
|
|
| ctx.globalAlpha = app.canvas.editor_alpha;
|
| ctx.restore();
|
| }
|
|
|
| serializeValue(serializedNode: SerializedLGraphNode, widgetIndex: number) {
|
| const v = { ...this.value };
|
|
|
|
|
| if (!this.showModelAndClip) {
|
| delete (v as any).strengthTwo;
|
| } else {
|
| this.value.strengthTwo = this.value.strengthTwo ?? 1;
|
| v.strengthTwo = this.value.strengthTwo;
|
| }
|
| return v;
|
| }
|
|
|
| onToggleDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.value.on = !this.value.on;
|
| this.cancelMouseDown();
|
| return true;
|
| }
|
|
|
| onInfoDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.showLoraInfoDialog();
|
| }
|
|
|
| onLoraDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| showLoraChooser(event, (value: ContextMenuItem) => {
|
| if (typeof value === "string") {
|
| this.value.lora = value;
|
| this.loraInfo = null;
|
| this.getLoraInfo();
|
| }
|
| node.setDirtyCanvas(true, true);
|
| });
|
| this.cancelMouseDown();
|
| }
|
|
|
| onStrengthDecDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.stepStrength(-1, false);
|
| }
|
| onStrengthIncDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.stepStrength(1, false);
|
| }
|
| onStrengthTwoDecDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.stepStrength(-1, true);
|
| }
|
| onStrengthTwoIncDown(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.stepStrength(1, true);
|
| }
|
|
|
| onStrengthAnyMove(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.doOnStrengthAnyMove(event, false);
|
| }
|
|
|
| onStrengthTwoAnyMove(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.doOnStrengthAnyMove(event, true);
|
| }
|
|
|
| private doOnStrengthAnyMove(event: AdjustedMouseEvent, isTwo = false) {
|
| if (event.deltaX) {
|
| let prop: "strengthTwo" | "strength" = isTwo ? "strengthTwo" : "strength";
|
| this.haveMouseMovedStrength = true;
|
| this.value[prop] = (this.value[prop] ?? 1) + event.deltaX * 0.05;
|
| }
|
| }
|
|
|
| onStrengthValUp(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.doOnStrengthValUp(event, false);
|
| }
|
|
|
| onStrengthTwoValUp(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode) {
|
| this.doOnStrengthValUp(event, true);
|
| }
|
|
|
| private doOnStrengthValUp(event: AdjustedMouseEvent, isTwo = false) {
|
| if (this.haveMouseMovedStrength) return;
|
| let prop: "strengthTwo" | "strength" = isTwo ? "strengthTwo" : "strength";
|
| const canvas = app.canvas as LGraphCanvas;
|
| canvas.prompt("Value", this.value[prop], (v: string) => (this.value[prop] = Number(v)), event);
|
| }
|
|
|
| override onMouseUp(event: AdjustedMouseEvent, pos: Vector2, node: TLGraphNode): boolean | void {
|
| super.onMouseUp(event, pos, node);
|
| this.haveMouseMovedStrength = false;
|
| }
|
|
|
| showLoraInfoDialog() {
|
| if (!this.value.lora || this.value.lora === "None") {
|
| return;
|
| }
|
| const infoDialog = new RgthreeLoraInfoDialog(this.value.lora).show();
|
| infoDialog.addEventListener("close", ((e: CustomEvent<{ dirty: boolean }>) => {
|
| if (e.detail.dirty) {
|
| this.getLoraInfo(true);
|
| }
|
| }) as EventListener);
|
| }
|
|
|
| private stepStrength(direction: -1 | 1, isTwo = false) {
|
| let step = 0.05;
|
| let prop: "strengthTwo" | "strength" = isTwo ? "strengthTwo" : "strength";
|
| let strength = (this.value[prop] ?? 1) + step * direction;
|
| this.value[prop] = Math.round(strength * 100) / 100;
|
| }
|
|
|
| private getLoraInfo(force = false) {
|
| if (!this.loraInfoPromise || force == true) {
|
| let promise;
|
| if (this.value.lora && this.value.lora != "None") {
|
| promise = LORA_INFO_SERVICE.getInfo(this.value.lora, force, true);
|
| } else {
|
| promise = Promise.resolve(null);
|
| }
|
| this.loraInfoPromise = promise.then((v) => (this.loraInfo = v));
|
| }
|
| return this.loraInfoPromise;
|
| }
|
| }
|
|
|
|
|
| const NODE_CLASS = RgthreePowerLoraLoader;
|
|
|
|
|
| app.registerExtension({
|
| name: "rgthree.PowerLoraLoader",
|
| async beforeRegisterNodeDef(nodeType: ComfyNodeConstructor, nodeData: ComfyObjectInfo) {
|
| if (nodeData.name === NODE_CLASS.type) {
|
| NODE_CLASS.setUp(nodeType, nodeData);
|
| }
|
| },
|
| });
|
|
|