diff --git a/javascript/active_units.js b/javascript/active_units.js new file mode 100644 index 000000000..03b8be0af --- /dev/null +++ b/javascript/active_units.js @@ -0,0 +1,73 @@ +/** + * [TODO] Give a badge on ControlNet Accordion indicating total number of active + * units. + * Give a dot indicator on each ControlNet unit tab, indicating whether + * the unit is active. + */ +const cnetAllUnits = new Map/* */(); + +onUiUpdate(() => { + function childIndex(element) { + // Get all child nodes of the parent + let children = Array.from(element.parentNode.childNodes); + + // Filter out non-element nodes (like text nodes and comments) + children = children.filter(child => child.nodeType === Node.ELEMENT_NODE); + + return children.indexOf(element); + } + + class GradioTab { + constructor(tab) { + this.enabledCheckbox = tab.querySelector('.cnet-unit-enabled input'); + const tabs = tab.parentNode; + this.tabNav = tabs.querySelector('.tab-nav'); + this.tabIndex = childIndex(tab) - 1; // -1 because tab-nav is also at the same level. + + this.attachEnabledButtonListener(); + this.attachTabNavChangeObserver(); + } + + getTabNavButton() { + return this.tabNav.querySelector(`:nth-child(${this.tabIndex + 1})`); + } + + applyActiveState() { + const tabNavButton = this.getTabNavButton(); + if (!tabNavButton) return; + + if (this.enabledCheckbox.checked) { + tabNavButton.classList.add('cnet-unit-active'); + } else { + tabNavButton.classList.remove('cnet-unit-active'); + } + } + + attachEnabledButtonListener() { + this.enabledCheckbox.addEventListener('change', () => { + this.applyActiveState(); + }); + } + + /** + * Each time the active tab change, all tab nav buttons are cleared and + * regenerated by gradio. So we need to reapply the active states on + * them. + */ + attachTabNavChangeObserver() { + const observer = new MutationObserver((mutationsList, observer) => { + for(const mutation of mutationsList) { + if (mutation.type === 'childList') { + this.applyActiveState(); + } + } + }); + observer.observe(this.tabNav, { childList: true }); + } + } + + gradioApp().querySelectorAll('.cnet-unit-tab').forEach(tab => { + if (cnetAllUnits.has(tab)) return; + cnetAllUnits.set(tab, new GradioTab(tab)); + }); +}); diff --git a/scripts/controlnet.py b/scripts/controlnet.py index d4598a0cb..375395fcf 100644 --- a/scripts/controlnet.py +++ b/scripts/controlnet.py @@ -234,7 +234,8 @@ def ui(self, is_img2img): if max_models > 1: with gr.Tabs(elem_id=f"{elem_id_tabname}_tabs"): for i in range(max_models): - with gr.Tab(f"ControlNet Unit {i}"): + with gr.Tab(f"ControlNet Unit {i}", + elem_classes=['cnet-unit-tab']): controls += (self.uigroup(f"ControlNet-{i}", is_img2img, elem_id_tabname),) else: with gr.Column(): diff --git a/scripts/controlnet_ui/controlnet_ui_group.py b/scripts/controlnet_ui/controlnet_ui_group.py index 18be13c8a..a114529ee 100644 --- a/scripts/controlnet_ui/controlnet_ui_group.py +++ b/scripts/controlnet_ui/controlnet_ui_group.py @@ -260,6 +260,7 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: label="Enable", value=self.default_unit.enabled, elem_id=f"{elem_id_tabname}_{tabname}_controlnet_enable_checkbox", + elem_classes=['cnet-unit-enabled'], ) self.lowvram = gr.Checkbox( label="Low VRAM", diff --git a/style.css b/style.css index f051f7e87..26b07d4af 100644 --- a/style.css +++ b/style.css @@ -104,4 +104,13 @@ background: var(--background-fill-primary); height: var(--size-5); color: var(--block-label-text-color) !important; +} + +.cnet-unit-active { + color: green !important; + font-weight: bold !important; +} + +.dark .cnet-unit-active { + color: greenyellow !important; } \ No newline at end of file