new design for left click and wheel (#5566)

* new design for left click and wheel

* update snap

* fix import

* fix test

* default value

* fix test

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Terry Jia
2025-09-16 23:11:17 -04:00
committed by GitHub
parent ff5d0923ca
commit 6866e1277a
12 changed files with 123 additions and 351 deletions

View File

@@ -1012,6 +1012,8 @@ test.describe('Canvas Navigation', () => {
test('Shift + mouse wheel should pan canvas horizontally', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.Canvas.MouseWheelScroll', 'panning')
await comfyPage.page.click('canvas')
await comfyPage.nextFrame()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -2348,7 +2348,7 @@ export class LGraphCanvas
if (
ctrlOrMeta &&
!e.altKey &&
LiteGraph.canvasNavigationMode === 'legacy'
LiteGraph.leftMouseClickBehavior === 'panning'
) {
this.#setupNodeSelectionDrag(e, pointer, node)
@@ -2616,8 +2616,8 @@ export class LGraphCanvas
!pointer.onDrag &&
this.allow_dragcanvas
) {
// allow dragging canvas if canvas is not in standard, or read-only (pan mode in standard)
if (LiteGraph.canvasNavigationMode !== 'standard' || this.read_only) {
// allow dragging canvas based on leftMouseClickBehavior or read-only mode
if (LiteGraph.leftMouseClickBehavior === 'panning' || this.read_only) {
pointer.onClick = () => this.processSelect(null, e)
pointer.finally = () => (this.dragging_canvas = false)
this.dragging_canvas = true
@@ -3629,8 +3629,8 @@ export class LGraphCanvas
e.ctrlKey || (e.metaKey && navigator.platform.includes('Mac'))
const isZoomModifier = isCtrlOrMacMeta && !e.altKey && !e.shiftKey
if (isZoomModifier || LiteGraph.canvasNavigationMode === 'legacy') {
// Legacy mode or standard mode with ctrl - use wheel for zoom
if (isZoomModifier || LiteGraph.mouseWheelScroll === 'zoom') {
// Zoom mode or modifier key pressed - use wheel for zoom
if (isTrackpad) {
// Trackpad gesture - use smooth scaling
scale *= 1 + e.deltaY * (1 - this.zoom_speed) * 0.18
@@ -3645,7 +3645,6 @@ export class LGraphCanvas
this.ds.changeScale(scale, [e.clientX, e.clientY])
}
} else {
// Standard mode without ctrl - use wheel / gestures to pan
// Trackpads and mice work on significantly different scales
const factor = isTrackpad ? 0.18 : 0.008_333

View File

@@ -304,9 +304,14 @@ export class LiteGraphGlobal {
/**
* "standard": change the dragging on left mouse button click to select, enable middle-click or spacebar+left-click dragging
* "legacy": Enable dragging on left-click (original behavior)
* "custom": Use leftMouseClickBehavior and mouseWheelScroll settings
* @default "legacy"
*/
canvasNavigationMode: 'standard' | 'legacy' = 'legacy'
canvasNavigationMode: 'standard' | 'legacy' | 'custom' = 'legacy'
leftMouseClickBehavior: 'panning' | 'select' = 'panning'
mouseWheelScroll: 'panning' | 'zoom' = 'panning'
/**
* If `true`, widget labels and values will both be truncated (proportionally to size),

View File

@@ -1,333 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`LGraph configure() > LGraph matches previous snapshot (normal configure() usage) > configuredBasicGraph 1`] = `
LGraph {
"_groups": [
LGraphGroup {
"_bounding": Float32Array [
20,
20,
1,
3,
],
"_children": Set {},
"_nodes": [],
"_pos": Float32Array [
20,
20,
],
"_size": Float32Array [
1,
3,
],
"color": "#6029aa",
"flags": {},
"font": undefined,
"font_size": 14,
"graph": [Circular],
"id": 123,
"isPointInside": [Function],
"selected": undefined,
"setDirtyCanvas": [Function],
"title": "A group to test with",
},
],
"_input_nodes": undefined,
"_last_trigger_time": undefined,
"_links": Map {},
"_nodes": [
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
140,
60,
],
"action_call": undefined,
"action_triggered": undefined,
"badgePosition": "top-left",
"badges": [],
"bgcolor": undefined,
"block_delete": undefined,
"boxcolor": undefined,
"clip_area": undefined,
"clonable": undefined,
"color": undefined,
"console": undefined,
"exec_version": undefined,
"execute_triggered": undefined,
"flags": {},
"freeWidgetSpace": undefined,
"gotFocusAt": undefined,
"graph": [Circular],
"has_errors": undefined,
"id": 1,
"ignore_remove": undefined,
"inputs": [],
"last_serialization": undefined,
"locked": undefined,
"lostFocusAt": undefined,
"mode": 0,
"mouseOver": undefined,
"order": 0,
"outputs": [],
"progress": undefined,
"properties": {},
"properties_info": [],
"redraw_on_mouse": undefined,
"removable": undefined,
"resizable": undefined,
"selected": undefined,
"serialize_widgets": undefined,
"showAdvanced": undefined,
"strokeStyles": {
"error": [Function],
"selected": [Function],
},
"title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet",
"widgets": undefined,
"widgets_start_y": undefined,
"widgets_up": undefined,
},
],
"_nodes_by_id": {
"1": LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
140,
60,
],
"action_call": undefined,
"action_triggered": undefined,
"badgePosition": "top-left",
"badges": [],
"bgcolor": undefined,
"block_delete": undefined,
"boxcolor": undefined,
"clip_area": undefined,
"clonable": undefined,
"color": undefined,
"console": undefined,
"exec_version": undefined,
"execute_triggered": undefined,
"flags": {},
"freeWidgetSpace": undefined,
"gotFocusAt": undefined,
"graph": [Circular],
"has_errors": undefined,
"id": 1,
"ignore_remove": undefined,
"inputs": [],
"last_serialization": undefined,
"locked": undefined,
"lostFocusAt": undefined,
"mode": 0,
"mouseOver": undefined,
"order": 0,
"outputs": [],
"progress": undefined,
"properties": {},
"properties_info": [],
"redraw_on_mouse": undefined,
"removable": undefined,
"resizable": undefined,
"selected": undefined,
"serialize_widgets": undefined,
"showAdvanced": undefined,
"strokeStyles": {
"error": [Function],
"selected": [Function],
},
"title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet",
"widgets": undefined,
"widgets_start_y": undefined,
"widgets_up": undefined,
},
},
"_nodes_executable": [],
"_nodes_in_order": [
LGraphNode {
"_collapsed_width": undefined,
"_level": undefined,
"_pos": Float32Array [
10,
10,
],
"_posSize": Float32Array [
10,
10,
140,
60,
],
"_relative_id": undefined,
"_shape": undefined,
"_size": Float32Array [
140,
60,
],
"action_call": undefined,
"action_triggered": undefined,
"badgePosition": "top-left",
"badges": [],
"bgcolor": undefined,
"block_delete": undefined,
"boxcolor": undefined,
"clip_area": undefined,
"clonable": undefined,
"color": undefined,
"console": undefined,
"exec_version": undefined,
"execute_triggered": undefined,
"flags": {},
"freeWidgetSpace": undefined,
"gotFocusAt": undefined,
"graph": [Circular],
"has_errors": undefined,
"id": 1,
"ignore_remove": undefined,
"inputs": [],
"last_serialization": undefined,
"locked": undefined,
"lostFocusAt": undefined,
"mode": 0,
"mouseOver": undefined,
"order": 0,
"outputs": [],
"progress": undefined,
"properties": {},
"properties_info": [],
"redraw_on_mouse": undefined,
"removable": undefined,
"resizable": undefined,
"selected": undefined,
"serialize_widgets": undefined,
"showAdvanced": undefined,
"strokeStyles": {
"error": [Function],
"selected": [Function],
},
"title": "LGraphNode",
"title_buttons": [],
"type": "mustBeSet",
"widgets": undefined,
"widgets_start_y": undefined,
"widgets_up": undefined,
},
],
"_subgraphs": Map {},
"_version": 3,
"catch_errors": true,
"config": {},
"elapsed_time": 0.01,
"errors_in_execution": undefined,
"events": CustomEventTarget {},
"execution_time": undefined,
"execution_timer_id": undefined,
"extra": {},
"filter": undefined,
"fixedtime": 0,
"fixedtime_lapse": 0.01,
"globaltime": 0,
"id": "ca9da7d8-fddd-4707-ad32-67be9be13140",
"iteration": 0,
"last_update_time": 0,
"links": Map {},
"list_of_graphcanvas": null,
"nodes_actioning": [],
"nodes_executedAction": [],
"nodes_executing": [],
"revision": 0,
"runningtime": 0,
"starttime": 0,
"state": {
"lastGroupId": 123,
"lastLinkId": 0,
"lastNodeId": 1,
"lastRerouteId": 0,
},
"status": 1,
"vars": {},
"version": 1,
}
`;
exports[`LGraph configure() > LGraph matches previous snapshot (normal configure() usage) > configuredMinGraph 1`] = `
LGraph {
"_groups": [],
"_input_nodes": undefined,
"_last_trigger_time": undefined,
"_links": Map {},
"_nodes": [],
"_nodes_by_id": {},
"_nodes_executable": [],
"_nodes_in_order": [],
"_subgraphs": Map {},
"_version": 0,
"catch_errors": true,
"config": {},
"elapsed_time": 0.01,
"errors_in_execution": undefined,
"events": CustomEventTarget {},
"execution_time": undefined,
"execution_timer_id": undefined,
"extra": {},
"filter": undefined,
"fixedtime": 0,
"fixedtime_lapse": 0.01,
"globaltime": 0,
"id": "d175890f-716a-4ece-ba33-1d17a513b7be",
"iteration": 0,
"last_update_time": 0,
"links": Map {},
"list_of_graphcanvas": null,
"nodes_actioning": [],
"nodes_executedAction": [],
"nodes_executing": [],
"revision": 0,
"runningtime": 0,
"starttime": 0,
"state": {
"lastGroupId": 0,
"lastLinkId": 0,
"lastNodeId": 0,
"lastRerouteId": 0,
},
"status": 1,
"vars": {},
"version": 1,
}
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`LGraph configure() > LGraph matches previous snapshot (normal configure() usage) > configuredBasicGraph 1`] = `
LGraph {
"_groups": [

View File

@@ -30,7 +30,7 @@
"tooltip": "Image URL for the canvas background. You can right-click an image in the outputs panel and select \"Set as Background\" to use it, or upload your own image using the upload button."
},
"Comfy_Canvas_NavigationMode": {
"name": "Canvas Navigation Mode",
"name": "Navigation Mode",
"options": {
"Standard (New)": "Standard (New)",
"Drag Navigation": "Drag Navigation"

View File

@@ -131,11 +131,26 @@ export const useLitegraphSettings = () => {
const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode') as
| 'standard'
| 'legacy'
| 'custom'
LiteGraph.canvasNavigationMode = navigationMode
LiteGraph.macTrackpadGestures = navigationMode === 'standard'
})
watchEffect(() => {
const leftMouseBehavior = settingStore.get(
'Comfy.Canvas.LeftMouseClickBehavior'
) as 'panning' | 'select'
LiteGraph.leftMouseClickBehavior = leftMouseBehavior
})
watchEffect(() => {
const mouseWheelScroll = settingStore.get(
'Comfy.Canvas.MouseWheelScroll'
) as 'panning' | 'zoom'
LiteGraph.mouseWheelScroll = mouseWheelScroll
})
watchEffect(() => {
LiteGraph.saveViewportWithGraph = settingStore.get(
'Comfy.EnableWorkflowViewRestore'

View File

@@ -1,4 +1,5 @@
import { LinkMarkerShape, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { SettingParams } from '@/platform/settings/types'
import type { ColorPalettes } from '@/schemas/colorPaletteSchema'
import type { Keybinding } from '@/schemas/keyBindingSchema'
@@ -138,6 +139,95 @@ export const CORE_SETTINGS: SettingParams[] = [
type: 'boolean',
defaultValue: false
},
{
id: 'Comfy.Canvas.NavigationMode',
category: ['LiteGraph', 'Canvas Navigation', 'NavigationMode'],
name: 'Navigation Mode',
defaultValue: 'legacy',
type: 'combo',
sortOrder: 100,
options: [
{ value: 'standard', text: 'Standard (New)' },
{ value: 'legacy', text: 'Drag Navigation' },
{ value: 'custom', text: 'Custom' }
],
versionAdded: '1.25.0',
defaultsByInstallVersion: {
'1.25.0': 'legacy'
},
onChange: async (newValue: string) => {
const settingStore = useSettingStore()
if (newValue === 'standard') {
// Update related settings to match standard mode - select + panning
await settingStore.set('Comfy.Canvas.LeftMouseClickBehavior', 'select')
await settingStore.set('Comfy.Canvas.MouseWheelScroll', 'panning')
} else if (newValue === 'legacy') {
// Update related settings to match legacy mode - panning + zoom
await settingStore.set('Comfy.Canvas.LeftMouseClickBehavior', 'panning')
await settingStore.set('Comfy.Canvas.MouseWheelScroll', 'zoom')
}
}
},
{
id: 'Comfy.Canvas.LeftMouseClickBehavior',
category: ['LiteGraph', 'Canvas Navigation', 'LeftMouseClickBehavior'],
name: 'Left Mouse Click Behavior',
defaultValue: 'panning',
type: 'radio',
sortOrder: 50,
options: [
{ value: 'panning', text: 'Panning' },
{ value: 'select', text: 'Select' }
],
versionAdded: '1.27.4',
onChange: async (newValue: string) => {
const settingStore = useSettingStore()
const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode')
if (navigationMode !== 'custom') {
if (
(newValue === 'select' && navigationMode === 'standard') ||
(newValue === 'panning' && navigationMode === 'legacy')
) {
return
}
// only set to custom if it doesn't match the preset modes
await settingStore.set('Comfy.Canvas.NavigationMode', 'custom')
}
}
},
{
id: 'Comfy.Canvas.MouseWheelScroll',
category: ['LiteGraph', 'Canvas Navigation', 'MouseWheelScroll'],
name: 'Mouse Wheel Scroll',
defaultValue: 'zoom',
type: 'radio',
options: [
{ value: 'panning', text: 'Panning' },
{ value: 'zoom', text: 'Zoom in/out' }
],
versionAdded: '1.27.4',
onChange: async (newValue: string) => {
const settingStore = useSettingStore()
const navigationMode = settingStore.get('Comfy.Canvas.NavigationMode')
if (navigationMode !== 'custom') {
if (
(newValue === 'panning' && navigationMode === 'standard') ||
(newValue === 'zoom' && navigationMode === 'legacy')
) {
return
}
// only set to custom if it doesn't match the preset modes
await settingStore.set('Comfy.Canvas.NavigationMode', 'custom')
}
}
},
{
id: 'Comfy.Graph.CanvasInfo',
category: ['LiteGraph', 'Canvas', 'CanvasInfo'],
@@ -813,21 +903,6 @@ export const CORE_SETTINGS: SettingParams[] = [
defaultValue: 8,
versionAdded: '1.26.7'
},
{
id: 'Comfy.Canvas.NavigationMode',
category: ['LiteGraph', 'Canvas', 'CanvasNavigationMode'],
name: 'Canvas Navigation Mode',
defaultValue: 'legacy',
type: 'combo',
options: [
{ value: 'standard', text: 'Standard (New)' },
{ value: 'legacy', text: 'Drag Navigation' }
],
versionAdded: '1.25.0',
defaultsByInstallVersion: {
'1.25.0': 'legacy'
}
},
{
id: 'Comfy.Canvas.SelectionToolbox',
category: ['LiteGraph', 'Canvas', 'SelectionToolbox'],

View File

@@ -469,6 +469,8 @@ const zSettings = z.object({
'Comfy.Minimap.RenderBypassState': z.boolean(),
'Comfy.Minimap.RenderErrorState': z.boolean(),
'Comfy.Canvas.NavigationMode': z.string(),
'Comfy.Canvas.LeftMouseClickBehavior': z.string(),
'Comfy.Canvas.MouseWheelScroll': z.string(),
'Comfy.VueNodes.Enabled': z.boolean(),
'Comfy.Assets.UseAssetAPI': z.boolean(),
'Comfy-Desktop.AutoUpdate': z.boolean(),

View File

@@ -155,9 +155,11 @@ LiteGraphGlobal {
"do_add_triggers_slots": false,
"highlight_selected_group": true,
"isInsideRectangle": [Function],
"leftMouseClickBehavior": "panning",
"macGesturesRequireMac": true,
"macTrackpadGestures": false,
"middle_click_slot_add_default_node": false,
"mouseWheelScroll": "panning",
"node_box_coloured_by_mode": false,
"node_box_coloured_when_on": false,
"node_images_path": "",