refactor: rename package to comfyui_touchpad_scroll_controller and update related files

handle two finger scrolling directly in this plugin as to not require others to work
This commit is contained in:
Josh Whaley
2025-03-16 14:18:20 -04:00
parent 36528ac575
commit ad60b396f3
10 changed files with 207 additions and 100 deletions
+1 -1
View File
@@ -6,4 +6,4 @@ recursive-exclude * *.py[co]
recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif
graft src/comfyui_scroll_over_textarea/web
graft src/comfyui_touchpad_scroll_controller/web
+11 -5
View File
@@ -1,6 +1,6 @@
# ComfyUI Scroll Over Textarea
# ComfyUI Touchpad Scroll Controller
A ComfyUI extension to allow textarea elements to be scrolled over. Useful when using a trackpad in order to prevent accidental forward/back navigation (two fingers horizontally on a Mac) when scrolling around the UI.
A ComfyUI extension that enhances touchpad navigation by redirecting two-finger scrolling over to the canvas, including over textareas. This can prevent accidental back/forward browser navigation when using horizontal touchpad gestures and provides smooth zooming and panning for Mac and other touchpad users.
> [!NOTE]
> This projected was created with a [cookiecutter](https://github.com/Comfy-Org/cookiecutter-comfy-extension) template. It helps you start writing custom nodes without worrying about the Python setup.
@@ -14,14 +14,20 @@ A ComfyUI extension to allow textarea elements to be scrolled over. Useful when
# Features
- A list of features
- Optimizes touchpad two-finger scrolling behavior for ComfyUI canvas navigation
- Prevents accidental browser back/forward navigation when using horizontal touchpad gestures
- Redirects two-finger scrolling over textareas to pan the canvas instead (configurable in options)
- Implements smoother touchpad-friendly zoom with ⌃+two-finger scroll or pinch gestures
- Implements a finer-grained zooming experience with ⌃+⌘+two-finger scroll or pinch gestures
- Customizable target selectors through settings
- Toggle functionality on/off through the settings panel
## Develop
To install the dev dependencies and pre-commit (will run the ruff hook), do:
```bash
cd comfyui_scroll_over_textarea
cd ComfyUI/custom_nodes/comfyui_touchpad_scroll_controller
pip install -e .[dev]
pre-commit install
```
@@ -43,7 +49,7 @@ git push
## Writing custom nodes
An example custom node is located in [node.py](src/comfyui_scroll_over_textarea/nodes.py). To learn more, read the [docs](https://docs.comfy.org/essentials/custom_node_overview).
An example custom node is located in [node.py](src/comfyui_touchpad_scroll_controller/nodes.py). To learn more, read the [docs](https://docs.comfy.org/essentials/custom_node_overview).
## Tests
+3 -3
View File
@@ -1,4 +1,4 @@
"""Top-level package for comfyui_scroll_over_textarea."""
"""Top-level package for comfyui_touchpad_scroll_controller."""
__all__ = [
"NODE_CLASS_MAPPINGS",
@@ -10,7 +10,7 @@ __author__ = """Josh Whaley"""
__email__ = "josh@whaleymail.net"
__version__ = "0.1.0"
from .src.comfyui_scroll_over_textarea.nodes import NODE_CLASS_MAPPINGS
from .src.comfyui_scroll_over_textarea.nodes import NODE_DISPLAY_NAME_MAPPINGS
from .src.comfyui_touchpad_scroll_controller.nodes import NODE_CLASS_MAPPINGS
from .src.comfyui_touchpad_scroll_controller.nodes import NODE_DISPLAY_NAME_MAPPINGS
WEB_DIRECTORY = "./web"
+7 -7
View File
@@ -3,9 +3,9 @@ requires = ["setuptools>=70.0"]
build-backend = "setuptools.build_meta"
[project]
name = "comfyui_scroll_over_textarea"
version = "0.1.0"
description = "A ComfyUI extension to allow textarea elements to be scrolled over. Useful when using a trackpad in order to prevent accidental forward/back navigation (two fingers horizontally on a Mac) when scrolling around the UI."
name = "comfyui_touchpad_scroll_controller"
version = "0.2.0"
description = "A ComfyUI extension that enhances touchpad navigation by redirecting two-finger scrolling over to the canvas, including over textareas. This can prevent accidental back/forward browser navigation when using horizontal touchpad gestures and provides smooth zooming and panning for Mac and other touchpad users."
authors = [
{name = "Josh Whaley", email = "josh@whaleymail.net"}
]
@@ -27,13 +27,13 @@ dev = [
]
[project.urls]
bugs = "https://github.com/joreyaesh/comfyui_scroll_over_textarea/issues"
homepage = "https://github.com/joreyaesh/comfyui_scroll_over_textarea"
Repository = "https://github.com/joreyaesh/comfyui_scroll_over_textarea"
bugs = "https://github.com/joreyaesh/comfyui_touchpad_scroll_controller/issues"
homepage = "https://github.com/joreyaesh/comfyui_touchpad_scroll_controller"
Repository = "https://github.com/joreyaesh/comfyui_touchpad_scroll_controller"
[tool.comfy]
PublisherId = "joreyaesh"
DisplayName = "ComfyUI Scroll Over Textarea"
DisplayName = "ComfyUI Touchpad Scroll Controller"
Icon = ""
[tool.setuptools.package-data]
@@ -1,7 +1,7 @@
from inspect import cleandoc
class ScrollOverTextareaDummyNode:
class TouchpadScrollControllerDummyNode:
"""
An empty dummy node since this has changes only for the UI.
@@ -68,7 +68,7 @@ class ScrollOverTextareaDummyNode:
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {"ScrollOverTextareaDummyNode": ScrollOverTextareaDummyNode}
NODE_CLASS_MAPPINGS = {"TouchpadScrollControllerDummyNode": TouchpadScrollControllerDummyNode}
# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {"ScrollOverTextareaDummyNode": "Scroll Over Textarea"}
NODE_DISPLAY_NAME_MAPPINGS = {"TouchpadScrollControllerDummyNode": "Touchpad Scroll Controller Dummy Node"}
@@ -1,24 +0,0 @@
#!/usr/bin/env python
"""Tests for `comfyui_scroll_over_textarea` package."""
import pytest
from src.comfyui_scroll_over_textarea.nodes import ScrollOverTextareaDummyNode
@pytest.fixture
def example_node():
"""Fixture to create an Example node instance."""
return ScrollOverTextareaDummyNode()
def test_example_node_initialization(example_node):
"""Test that the node can be instantiated."""
assert isinstance(example_node, ScrollOverTextareaDummyNode)
def test_return_types():
"""Test the node's metadata."""
assert ScrollOverTextareaDummyNode.RETURN_TYPES == ()
assert ScrollOverTextareaDummyNode.FUNCTION == "test"
assert ScrollOverTextareaDummyNode.CATEGORY == "Dummy"
@@ -0,0 +1,26 @@
#!/usr/bin/env python
"""Tests for `comfyui_touchpad_scroll_controller` package."""
import pytest
from src.comfyui_touchpad_scroll_controller.nodes import (
TouchpadScrollControllerDummyNode,
)
@pytest.fixture
def example_node():
"""Fixture to create an Example node instance."""
return TouchpadScrollControllerDummyNode()
def test_example_node_initialization(example_node):
"""Test that the node can be instantiated."""
assert isinstance(example_node, TouchpadScrollControllerDummyNode)
def test_return_types():
"""Test the node's metadata."""
assert TouchpadScrollControllerDummyNode.RETURN_TYPES == ()
assert TouchpadScrollControllerDummyNode.FUNCTION == "test"
assert TouchpadScrollControllerDummyNode.CATEGORY == "Dummy"
-57
View File
@@ -1,57 +0,0 @@
import { app } from '../../../scripts/app.js';
app.registerExtension({
name: 'ComfyUI Scroll Over Textarea',
settings: [
{
id: 'comfyui_scroll_over_textarea.enable',
name: 'Prevent Scrolling Within Textareas',
type: 'boolean',
defaultValue: true,
onChange: (newVal, oldVal) => {
updateTextareaScrollListener(newVal);
},
},
],
async setup() {
console.log('ComfyUI Scroll Over Textarea Extension Loaded');
const cb = (event) => {
app.canvas.processMouseWheel(event);
event.preventDefault();
};
app.canvas['_ignore_textarea_scroll_callback'] = cb;
updateTextareaScrollListener();
},
async nodeCreated(node) {
const enabled = app.extensionManager.setting.get(
'comfyui_scroll_over_textarea.enable'
);
setTimeout(() => {
updateTextareaScrollListener(enabled);
}, 50);
},
});
function updateTextareaScrollListener(
ignoreTextareaScroll = app.extensionManager.setting.get(
'comfyui_scroll_over_textarea.enable'
)
) {
const textareas = document.querySelectorAll('textarea.comfy-multiline-input');
textareas.forEach((textarea) => {
if (app.canvas) {
if (ignoreTextareaScroll) {
textarea.addEventListener(
'mousewheel',
app.canvas['_ignore_textarea_scroll_callback']
);
} else {
textarea.removeEventListener(
'mousewheel',
app.canvas['_ignore_textarea_scroll_callback']
);
}
}
});
}
@@ -0,0 +1,156 @@
// @ts-ignore
import { app } from '../../../scripts/app.js';
// Default selectors to target
const defaultTargetSelectors = [
'textarea.comfy-multiline-input',
'.p-panel.p-component',
];
app.registerExtension({
name: 'ComfyUI Touchpad Scroll Controller',
settings: [
{
id: 'comfyui_touchpad_scroll_controller.enableTextareas',
name: 'Ignore Textarea Scroll',
tooltip:
'Ignore mouse wheel events on textareas and pass the event to the canvas.',
type: 'boolean',
defaultValue: true,
onChange: (newVal, oldVal) => {
updateScrollListener({ ignoreTextareaScroll: newVal });
},
},
{
id: 'comfyui_touchpad_scroll_controller.enableTouchpadScroll',
name: 'Enable Touchpad Scroll Controller',
tooltip:
'Enable touchpad scroll controller. This will override the default mouse wheel behavior. NOTE: You may need to refresh the page after changing this setting.',
type: 'boolean',
defaultValue: true,
onChange: (newVal, oldVal) => {
updateScrollListener({ enableTouchpadScroll: newVal });
},
},
{
id: 'comfyui_touchpad_scroll_controller.selectors',
name: 'Target Selectors (Comma Separated)',
type: 'text',
defaultValue: defaultTargetSelectors.join(', '),
onChange: (newVal, oldVal) => {
updateScrollListener({ targetSelectors: getTargetSelectors(newVal) });
},
},
],
async setup() {
console.log('ComfyUI Touchpad Scroll Controller Extension Loaded');
const cb = (event) => {
app.canvas.processMouseWheel(event);
event.preventDefault();
};
app.canvas['_ignore_textarea_scroll_callback'] = cb;
updateScrollListener();
},
async nodeCreated(node) {
const enabled = app.extensionManager.setting.get(
'comfyui_touchpad_scroll_controller.enableTextareas'
);
setTimeout(() => {
updateScrollListener(enabled);
}, 50);
},
});
function getTargetSelectors(
selectorString = app.extensionManager.setting.get(
'comfyui_touchpad_scroll_controller.selectors'
) || defaultTargetSelectors.join(', ')
) {
return selectorString
.split(',')
.map((selector) => selector.trim())
.filter((selector) => selector.length > 0);
}
/**
* @param {{ignoreTextareaScroll?: boolean; targetSelectors?: string[], enableTouchpadScroll?: boolean}} param0
*/
function updateScrollListener({
ignoreTextareaScroll,
targetSelectors,
enableTouchpadScroll,
} = {}) {
const isTouchpadScrollEnabled =
enableTouchpadScroll ??
app.extensionManager.setting.get(
'comfyui_touchpad_scroll_controller.enableTouchpadScroll'
);
if (!isTouchpadScrollEnabled) {
return;
}
/**
* Smooth scrolling for touchpad
*/
// @ts-ignore
window.LGraphCanvas.prototype.processMouseWheel = function (
/** @type {WheelEvent}*/ event
) {
if (!this.graph || !this.allow_dragcanvas) return;
const { clientX: x, clientY: y } = event;
if (this.viewport) {
const [viewportX, viewportY, width, height] = this.viewport;
const isInsideX = x >= viewportX && x < viewportX + width;
const isInsideY = y >= viewportY && y < viewportY + height;
if (!(isInsideX && isInsideY)) return;
}
let scale = this.ds.scale;
let { deltaX, deltaY } = event;
if (event.metaKey || event.ctrlKey) {
let SCALE = event.ctrlKey ? 150 : 100;
if (event.metaKey) SCALE *= -1 / 0.5;
this.ds.changeScale(scale - deltaY / SCALE, [
event.clientX,
event.clientY,
]);
} else {
this.ds.mouseDrag(-deltaX, -deltaY);
}
this.graph.change();
event.preventDefault();
return false; // prevent default
};
const selectors = targetSelectors || getTargetSelectors();
const isTextareaScrollIgnored =
ignoreTextareaScroll ??
app.extensionManager.setting.get(
'comfyui_touchpad_scroll_controller.enableTextareas'
);
if (!isTouchpadScrollEnabled) {
selectors.forEach((selector) => {
const elements = document.querySelectorAll(selector);
elements.forEach((element) => {
if (app.canvas) {
if (isTextareaScrollIgnored) {
element.addEventListener(
'mousewheel',
app.canvas['_ignore_textarea_scroll_callback']
);
} else {
element.removeEventListener(
'mousewheel',
app.canvas['_ignore_textarea_scroll_callback']
);
}
}
});
});
}
}