diff --git a/CHANGELOG.md b/CHANGELOG.md index cc19c47..f6c5e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ All notable changes to ComfyUI-AusBoss are documented here. get its width, nodes side by side in a row get its height, and a grid gets both. It works in the classic look and in Nodes 2.0, one Ctrl+Z undoes it, and Settings → 🆎 AusBoss → Canvas → Resize together turns it off. +- **Integer 🆎 and Float 🆎 have a big look.** The gear on the node picks it, + as on Text 🆎: one large number in the middle of the node, for a setting + you change on every run. The value and what the node sends are the same in + both looks. - **New node: H3 Prompt Writer.** Type one line for each thing that happens, give each picture and video a job, and it writes the prompt MiniMax H3 expects, with the width and height to match. For a video with two people, diff --git a/js/docs/AUSBOSS_NODES_Float.md b/js/docs/AUSBOSS_NODES_Float.md index 52eaf3d..9b21404 100644 --- a/js/docs/AUSBOSS_NODES_Float.md +++ b/js/docs/AUSBOSS_NODES_Float.md @@ -8,6 +8,11 @@ every node that needs it, and change it in one place. - **Value** (`value`): The number to send out. It starts at `0`. The box shows the decimals its steps use (`0.00`) and holds up to three, so a typed `0.125` stays `0.125`. +- The gear, right: **Look**. **normal** is the usual box with its label. + **big** shows the number large in the middle of the node, with an arrow + on each side, for a setting people change on every run, such as the + length of a clip. A drag and typing work the same. Each node keeps its own look, and it is + saved with the workflow. ## Limits @@ -44,3 +49,7 @@ into **Math Expression 🆎** instead of retyping derived numbers. API is passed on as it is. - Wired only to inputs that share no value (one stops at 1, the other starts at 2), the box keeps no limits. +- The look is stored in the node's properties as `ausboss_text_look: "big"`, the + same as Text 🆎. It is not an input, so the saved value and what the node + sends are the same in both looks, and an older version of the pack shows + the usual box. diff --git a/js/docs/AUSBOSS_NODES_Integer.md b/js/docs/AUSBOSS_NODES_Integer.md index c27114e..f900bc4 100644 --- a/js/docs/AUSBOSS_NODES_Integer.md +++ b/js/docs/AUSBOSS_NODES_Integer.md @@ -8,6 +8,11 @@ wire it to every node that needs it. - **Value** (`value`): The whole number to send out. It starts at `0`. The arrows and a drag move it by 1, or click and type. It holds numbers as big as a seed. +- The gear, right: **Look**. **normal** is the usual box with its label. + **big** shows the number large in the middle of the node, with an arrow + on each side, for a setting people change on every run, such as the + length of a clip. A drag and typing work the same. Each node keeps its own look, and it is + saved with the workflow. ## Limits @@ -40,3 +45,7 @@ For arithmetic on the way — halving a size, adding a margin — feed this into - On its own the box goes up to 9,007,199,254,740,991, the largest whole number a browser holds exactly. It stopped at 2,147,483,647 before, which most seeds are above. +- The look is stored in the node's properties as `ausboss_text_look: "big"`, the + same as Text 🆎. It is not an input, so the saved value and what the node + sends are the same in both looks, and an older version of the pack shows + the usual box. diff --git a/js/shared/text_look.mjs b/js/shared/text_look.mjs index 079e069..0748e11 100644 --- a/js/shared/text_look.mjs +++ b/js/shared/text_look.mjs @@ -1,6 +1,8 @@ // The Text node's two looks, chosen per node from the gear on its card: the // usual text box (the default), or "big", a word or two in large centred -// letters that are easy to spot, for a label typed over a picture. +// letters that are easy to spot, for a label typed over a picture. Integer +// and Float take the same two looks, kept in the same property: big is one +// large centred number, for a setting people change on every run. // // The choice is kept in the node's properties, so it is saved with the // workflow and nothing else changes: the node's inputs, its saved widget @@ -47,6 +49,18 @@ export const TEXT_LOOK_MENU = [ }, ]; +// The same choice on Integer and Float, said for a number. +export const NUMBER_LOOK_MENU = [ + { + ...TEXT_LOOK_MENU[0], + hint: "How this box shows its number. Stored on this node.", + optionHints: { + normal: "the usual number box with its label.", + big: "one large centred number, for a setting you change on every run.", + }, + }, +]; + // Room the gear takes from the card's control column. The usual box keeps // the gear beside it; the big field spans the card and the gear sits in its // top corner instead. diff --git a/js/shared/widget_card.mjs b/js/shared/widget_card.mjs index 65957bf..865db67 100644 --- a/js/shared/widget_card.mjs +++ b/js/shared/widget_card.mjs @@ -68,6 +68,10 @@ const BIG_FONT = 22; const BIG_LINE = 26; const BIG_PAD_Y = 4; const BIG_PAD_X = 30; +// A number row in the big look (Integer and Float): one large centred number +// between a step arrow on each side, in a field as wide as the card, with the +// gear in its corner as on Text. +const BIG_NUMBER_FONT = 30; export function ensureCardCss() { if (document.getElementById(CSS_ID)) return; @@ -123,6 +127,19 @@ export function ensureCardCss() { .ausboss-card.look-big .ausboss-card-row.area .ausboss-card-field:focus-within{border-color:${BRAND}} .ausboss-card.look-big .ausboss-card-area{position:absolute;left:0;right:0;top:0;bottom:0;margin:auto 0;max-height:100%;padding:${BIG_PAD_Y}px ${BIG_PAD_X}px;border:none;border-radius:5px;background:transparent;color:#6fe0d6;text-shadow:0 0 .35em rgba(0,180,170,.55);font:600 ${BIG_FONT}px/${BIG_LINE}px ${FONT};text-align:center;scrollbar-width:thin;scrollbar-color:#2f3d3c transparent} .ausboss-card.look-big .ausboss-card-area::placeholder{color:#4f6462;text-shadow:none} +.ausboss-card.look-big .ausboss-card-row:not(.area){display:block} +.ausboss-card.look-big .ausboss-card-row:not(.area) .ausboss-card-label{display:none} +.ausboss-card.look-big .ausboss-card-row:not(.area) .ausboss-card-control{margin:0} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub{height:100%;justify-content:center;align-items:center;gap:4px;border-color:#2a3437;border-radius:6px;background:#0b0f10} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub:focus-within{border-color:${BRAND}} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-input{order:1;flex:0 0 auto;width:auto;min-width:2ch;max-width:60%;field-sizing:content;color:#6fe0d6;text-shadow:0 0 .35em rgba(0,180,170,.55);font:600 ${BIG_NUMBER_FONT}px ${FONT}} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-unit{order:2} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-unit:empty{display:none} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-step{display:contents} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-step>button{flex:none;width:30px;height:30px;border-radius:15px;color:#6f8886} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-step>button:first-child{order:3} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-step>button:last-child{order:0} +.ausboss-card.look-big .ausboss-card-field>.ausboss-scrub>.ausboss-scrub-step>button>svg{transform:rotate(90deg) scale(1.6)} .ausboss-card.look-big .ausboss-card-corner{top:${CARD_PADDING + 4}px;right:12px;height:22px} .ausboss-card.look-big .ausboss-card-gear{width:22px;height:22px;border-color:transparent;background:transparent} .ausboss-card.look-big .ausboss-card-gear:hover{border-color:${BRAND}} @@ -723,9 +740,10 @@ export function mountWidgetCard(node, { rows, minWidth = 300, first = false, hid if (state.look) root.classList.remove(`look-${state.look}`); if (look) root.classList.add(`look-${look}`); state.look = look; - // A textarea row takes its height in this look. + // A textarea row, and any row with a height of its own in some look, + // takes its height in this look. for (const entry of state.rows) { - if (entry.row.kind !== "textarea") continue; + if (entry.row.kind !== "textarea" && !entry.row.looks) continue; const height = `${rowHeight(entry.row, look)}px`; if (entry.row.grow) entry.element.style.minHeight = height; else entry.element.style.height = height; diff --git a/js/widget_cards/index.js b/js/widget_cards/index.js index a7fd29d..38170ad 100644 --- a/js/widget_cards/index.js +++ b/js/widget_cards/index.js @@ -16,7 +16,7 @@ import { SOCKETLESS_CHOICE, SOCKETLESS_TEXT, commitWidgetValue, markSocketless, import { mediaViewQuery } from "../shared/media_list.mjs"; import { gearIconSvg, openSettingsMenu } from "../shared/settings_menu.mjs"; import { SEAM_MENU, SEAM_MUTE_TITLES, isBlendIn, seamCornerReserve } from "../shared/stitch_seam.mjs"; -import { TEXT_LOOK_MENU, setTextLook, textLook, textLookCornerReserve } from "../shared/text_look.mjs"; +import { NUMBER_LOOK_MENU, TEXT_LOOK_MENU, setTextLook, textLook, textLookCornerReserve } from "../shared/text_look.mjs"; import { fineEdgeLabel } from "../shared/touch_math.mjs"; import { valueToTake, wiredLimits } from "../shared/wired_limits.mjs"; @@ -69,20 +69,21 @@ function seamCorner(node, card) { }, }; } -// Text: the gear in the card's corner picks the node's look, the usual box -// or big centred letters (shared/text_look.mjs). The look is kept on the -// node, not in a widget, so the node's inputs and saved values never change. -function textLookCorner(node, card) { +// Text, Integer and Float: the gear in the card's corner picks the node's +// look, the usual box or big and centred (shared/text_look.mjs). The look is +// kept on the node, not in a widget, so the node's inputs and saved values +// never change. +const lookCorner = (schema, title) => (node, card) => { const gear = document.createElement("button"); gear.type = "button"; gear.className = "ausboss-card-gear"; - gear.title = "Text settings"; + gear.title = title; gear.innerHTML = gearIconSvg(); gear.addEventListener("click", () => openSettingsMenu({ scope: "text_look", - schema: TEXT_LOOK_MENU, + schema, anchor: gear.getBoundingClientRect(), - title: "Text settings", + title, initial: { look: textLook(node) }, onChange: (values) => { if (values.look === textLook(node)) return; @@ -93,7 +94,9 @@ function textLookCorner(node, card) { }, })); return { element: gear, sync: () => textLookCornerReserve(textLook(node)) }; -} +}; +const textLookCorner = lookCorner(TEXT_LOOK_MENU, "Text settings"); +const numberLookCorner = lookCorner(NUMBER_LOOK_MENU, "Number settings"); // Save Video rows that only some formats read (crf, save_metadata). const formatReads = (name) => (values) => formatWidgetVisibility(String(values.format))[name]; @@ -306,9 +309,16 @@ const CARDS = { // Like ComfyUI's own Primitive node, both take over the limits of the // input they are wired to: wired to a sampler's denoise, Float stops at 1. // A new box also takes the value of the first input it is wired to - // (`takesWired`), so wiring it up does not change that setting. - AUSBOSS_NODES_Integer: { minWidth: 260, takesWired: "value", rows: [{ widget: "value", label: "Value", limits: wiredLimits }] }, - AUSBOSS_NODES_Float: { minWidth: 260, takesWired: "value", rows: [{ widget: "value", label: "Value", decimals: 3, trimZeros: true, limits: wiredLimits }] }, + // (`takesWired`), so wiring it up does not change that setting. The gear + // picks the look; big is one large centred number. + AUSBOSS_NODES_Integer: { + minWidth: 260, takesWired: "value", look: textLook, corner: numberLookCorner, + rows: [{ widget: "value", label: "Value", limits: wiredLimits, looks: { big: { height: 56 } } }], + }, + AUSBOSS_NODES_Float: { + minWidth: 260, takesWired: "value", look: textLook, corner: numberLookCorner, + rows: [{ widget: "value", label: "Value", decimals: 3, trimZeros: true, limits: wiredLimits, looks: { big: { height: 56 } } }], + }, // The gear picks the look; big is a compact field of large centred // letters, for a word or two over a picture. AUSBOSS_NODES_Text: { diff --git a/tests/scrub_input.test.mjs b/tests/scrub_input.test.mjs index 44ced4e..05842f7 100644 --- a/tests/scrub_input.test.mjs +++ b/tests/scrub_input.test.mjs @@ -179,7 +179,7 @@ test("zeroes the steps never reach are dropped, a value that uses them is shown test("Float 🆎 drops the zeroes its steps never reach", () => { const cards = readFileSync(join(ROOT, "js", "widget_cards", "index.js"), "utf-8"); - assert.match(cards, /AUSBOSS_NODES_Float: \{[^\n]*\btrimZeros: true\b/); + assert.match(cards, /AUSBOSS_NODES_Float: \{[\s\S]{0,200}?\btrimZeros: true\b/); }); test("a sum typed into a number box comes to its answer", () => { diff --git a/tests/text_look.test.mjs b/tests/text_look.test.mjs index 8c8f5ca..6c54422 100644 --- a/tests/text_look.test.mjs +++ b/tests/text_look.test.mjs @@ -7,7 +7,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { - TEXT_LOOKS, TEXT_LOOK_MENU, TEXT_LOOK_PROPERTY, setTextLook, textLook, textLookCornerReserve, + NUMBER_LOOK_MENU, TEXT_LOOKS, TEXT_LOOK_MENU, TEXT_LOOK_PROPERTY, setTextLook, textLook, textLookCornerReserve, } from "../js/shared/text_look.mjs"; import { nodeHeightAfterCardChange } from "../js/shared/panel_layout.mjs"; import { CARD_PADDING, cardChangeRestore, cardHeight, rowHeight } from "../js/shared/widget_card_math.mjs"; @@ -149,3 +149,55 @@ test("the look adds no input: the Text node still takes its one text box", () => const api = JSON.parse(read("tests", "fixtures", "node_api.json")); assert.deepEqual(api.AUSBOSS_NODES_Text, { required: ["text"], optional: [], outputs: [["STRING", "text"]] }); }); + +// Integer and Float take the same two looks from their own gear. +function card(name) { + const source = read("js", "widget_cards", "index.js"); + const start = source.indexOf(`\n ${name}: {`); + assert.ok(start >= 0, `js/widget_cards/index.js has no card for ${name}`); + const next = source.slice(start + 1).search(/\n {2}(\/\/|AUSBOSS_NODES_\w+: \{)/); + return source.slice(start, start + 1 + next); +} + +test("Integer and Float take the big look from the node, with the number gear in the corner", () => { + for (const name of ["AUSBOSS_NODES_Integer", "AUSBOSS_NODES_Float"]) { + const entry = card(name); + assert.match(entry, /look: textLook/, name); + assert.match(entry, /corner: numberLookCorner/, name); + assert.match(entry, /looks: \{ big: \{ height: 56 \} \}/, name); + // The usual box is unchanged. + assert.match(entry, /widget: "value", label: "Value"/, name); + } +}); + +test("the number gear offers the same looks, said for a number", () => { + const [look] = NUMBER_LOOK_MENU; + assert.equal(NUMBER_LOOK_MENU.length, 1); + assert.equal(look.key, "look"); + assert.deepEqual(look.options, TEXT_LOOKS); + assert.equal(look.default, "normal"); + assert.equal(look.persist, false); + assert.match(look.optionHints.big, /number/); + // A big number row is taller than a usual one and the card grows with it. + const row = { widget: "value", label: "Value", looks: { big: { height: 56 } } }; + assert.equal(rowHeight(row, "big"), 56); + assert.ok(cardHeight([row], "big") > cardHeight([row], "normal")); +}); + +test("a row with a height of its own in a look takes it when the look changes", () => { + const source = read("js", "shared", "widget_card.mjs"); + assert.match(source, /entry\.row\.kind !== "textarea" && !entry\.row\.looks/); +}); + +test("a big number sits between its two arrows, in a field as wide as the card", () => { + const source = read("js", "shared", "widget_card.mjs"); + const rule = (tail) => source.match(new RegExp(`\\n\\.ausboss-card\\.look-big \\.ausboss-card-field>\\.ausboss-scrub${tail}\\{([^\\n]*)`))?.[1] ?? ""; + assert.match(rule(""), /justify-content:center/); + assert.match(rule(">\\.ausboss-scrub-step"), /display:contents/); + // Down on the left, up on the right, the number between them. + assert.match(rule(">\\.ausboss-scrub-step>button:last-child"), /order:0/); + assert.match(rule(">\\.ausboss-scrub-input"), /order:1/); + assert.match(rule(">\\.ausboss-scrub-step>button:first-child"), /order:3/); + // No room held at the sides: the gear sits in the field's corner. + assert.match(source, /look-big \.ausboss-card-row:not\(\.area\) \.ausboss-card-control\{margin:0\}/); +});