From ad60b396f331fc75a942073df2b7de43f12bf54f Mon Sep 17 00:00:00 2001 From: Josh Whaley Date: Sun, 16 Mar 2025 14:18:20 -0400 Subject: [PATCH] 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 --- MANIFEST.in | 2 +- README.md | 16 +- __init__.py | 6 +- pyproject.toml | 14 +- .../__init__.py | 0 .../nodes.py | 6 +- tests/test_comfyui_scroll_over_textarea.py | 24 --- ...test_comfyui_touchpad_scroll_controller.py | 26 +++ web/js/comfyui_scroll_over_textarea.js | 57 ------- web/js/comfyui_touchpad_scroll_controller.js | 156 ++++++++++++++++++ 10 files changed, 207 insertions(+), 100 deletions(-) rename src/{comfyui_scroll_over_textarea => comfyui_touchpad_scroll_controller}/__init__.py (100%) rename src/{comfyui_scroll_over_textarea => comfyui_touchpad_scroll_controller}/nodes.py (92%) delete mode 100644 tests/test_comfyui_scroll_over_textarea.py create mode 100644 tests/test_comfyui_touchpad_scroll_controller.py delete mode 100644 web/js/comfyui_scroll_over_textarea.js create mode 100644 web/js/comfyui_touchpad_scroll_controller.js diff --git a/MANIFEST.in b/MANIFEST.in index a7f04ff..0d20fe4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -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 diff --git a/README.md b/README.md index 0ce0e8d..1a12c5c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/__init__.py b/__init__.py index 4f5cc7a..554c4df 100644 --- a/__init__.py +++ b/__init__.py @@ -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" diff --git a/pyproject.toml b/pyproject.toml index a59c413..a70cc50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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] diff --git a/src/comfyui_scroll_over_textarea/__init__.py b/src/comfyui_touchpad_scroll_controller/__init__.py similarity index 100% rename from src/comfyui_scroll_over_textarea/__init__.py rename to src/comfyui_touchpad_scroll_controller/__init__.py diff --git a/src/comfyui_scroll_over_textarea/nodes.py b/src/comfyui_touchpad_scroll_controller/nodes.py similarity index 92% rename from src/comfyui_scroll_over_textarea/nodes.py rename to src/comfyui_touchpad_scroll_controller/nodes.py index d612f24..d7525c1 100644 --- a/src/comfyui_scroll_over_textarea/nodes.py +++ b/src/comfyui_touchpad_scroll_controller/nodes.py @@ -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"} diff --git a/tests/test_comfyui_scroll_over_textarea.py b/tests/test_comfyui_scroll_over_textarea.py deleted file mode 100644 index 5d97718..0000000 --- a/tests/test_comfyui_scroll_over_textarea.py +++ /dev/null @@ -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" diff --git a/tests/test_comfyui_touchpad_scroll_controller.py b/tests/test_comfyui_touchpad_scroll_controller.py new file mode 100644 index 0000000..b78bf3f --- /dev/null +++ b/tests/test_comfyui_touchpad_scroll_controller.py @@ -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" diff --git a/web/js/comfyui_scroll_over_textarea.js b/web/js/comfyui_scroll_over_textarea.js deleted file mode 100644 index 8b47ad5..0000000 --- a/web/js/comfyui_scroll_over_textarea.js +++ /dev/null @@ -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'] - ); - } - } - }); -} diff --git a/web/js/comfyui_touchpad_scroll_controller.js b/web/js/comfyui_touchpad_scroll_controller.js new file mode 100644 index 0000000..6d7439c --- /dev/null +++ b/web/js/comfyui_touchpad_scroll_controller.js @@ -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'] + ); + } + } + }); + }); + } +}