mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-18 19:39:45 +00:00
BE change https://github.com/Comfy-Org/ComfyUI/pull/13322 ## Summary Add RANGE widget for image levels adjustment - Add RangeEditor widget with three display modes: plain, gradient, and histogram - Support optional midpoint (gamma) control for non-linear midtone adjustment - Integrate histogram display from upstream node outputs ## Screenshots (if applicable) <img width="1450" height="715" alt="image" src="https://github.com/user-attachments/assets/864976af-9eb7-4dd0-9ce1-2f5d7f003117" /> <img width="1431" height="701" alt="image" src="https://github.com/user-attachments/assets/7ee2af65-f87a-407b-8bf2-6ec59a1dff59" /> <img width="705" height="822" alt="image" src="https://github.com/user-attachments/assets/7bcb8f17-795f-498a-9f8a-076ed6c05a98" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10936-Range-editor-33b6d73d365081089e8be040b40f6c8a) by [Unito](https://www.unito.io)
114 lines
3.2 KiB
TypeScript
114 lines
3.2 KiB
TypeScript
import { onBeforeUnmount, ref } from 'vue'
|
|
import type { Ref } from 'vue'
|
|
|
|
import { clamp } from 'es-toolkit'
|
|
|
|
import { denormalize, normalize } from '@/utils/mathUtil'
|
|
import type { RangeValue } from '@/lib/litegraph/src/types/widgets'
|
|
|
|
type HandleType = 'min' | 'max' | 'midpoint'
|
|
|
|
interface UseRangeEditorOptions {
|
|
trackRef: Ref<HTMLElement | null>
|
|
modelValue: Ref<RangeValue>
|
|
valueMin: Ref<number>
|
|
valueMax: Ref<number>
|
|
showMidpoint: Ref<boolean>
|
|
}
|
|
|
|
export function useRangeEditor({
|
|
trackRef,
|
|
modelValue,
|
|
valueMin,
|
|
valueMax,
|
|
showMidpoint
|
|
}: UseRangeEditorOptions) {
|
|
const activeHandle = ref<HandleType | null>(null)
|
|
let cleanupDrag: (() => void) | null = null
|
|
|
|
function pointerToValue(e: PointerEvent): number {
|
|
const el = trackRef.value
|
|
if (!el) return valueMin.value
|
|
const rect = el.getBoundingClientRect()
|
|
const normalized = clamp((e.clientX - rect.left) / rect.width, 0, 1)
|
|
return denormalize(normalized, valueMin.value, valueMax.value)
|
|
}
|
|
|
|
function nearestHandle(value: number): HandleType {
|
|
const { min, max, midpoint } = modelValue.value
|
|
const dMin = Math.abs(value - min)
|
|
const dMax = Math.abs(value - max)
|
|
let best: HandleType = dMin <= dMax ? 'min' : 'max'
|
|
const bestDist = Math.min(dMin, dMax)
|
|
if (midpoint !== undefined && showMidpoint.value) {
|
|
const midAbs = min + midpoint * (max - min)
|
|
if (Math.abs(value - midAbs) < bestDist) {
|
|
best = 'midpoint'
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
function updateValue(handle: HandleType, value: number) {
|
|
const current = modelValue.value
|
|
const clamped = clamp(value, valueMin.value, valueMax.value)
|
|
|
|
if (handle === 'min') {
|
|
modelValue.value = { ...current, min: Math.min(clamped, current.max) }
|
|
} else if (handle === 'max') {
|
|
modelValue.value = { ...current, max: Math.max(clamped, current.min) }
|
|
} else {
|
|
const range = current.max - current.min
|
|
const midNorm =
|
|
range > 0 ? normalize(clamped, current.min, current.max) : 0
|
|
const midpoint = clamp(midNorm, 0, 1)
|
|
modelValue.value = { ...current, midpoint }
|
|
}
|
|
}
|
|
|
|
function handleTrackPointerDown(e: PointerEvent) {
|
|
if (e.button !== 0) return
|
|
startDrag(nearestHandle(pointerToValue(e)), e)
|
|
}
|
|
|
|
function startDrag(handle: HandleType, e: PointerEvent) {
|
|
if (e.button !== 0) return
|
|
cleanupDrag?.()
|
|
|
|
activeHandle.value = handle
|
|
const el = trackRef.value
|
|
if (!el) return
|
|
|
|
el.setPointerCapture(e.pointerId)
|
|
|
|
const onMove = (ev: PointerEvent) => {
|
|
if (!activeHandle.value) return
|
|
updateValue(activeHandle.value, pointerToValue(ev))
|
|
}
|
|
|
|
const endDrag = () => {
|
|
if (!activeHandle.value) return
|
|
activeHandle.value = null
|
|
el.removeEventListener('pointermove', onMove)
|
|
el.removeEventListener('pointerup', endDrag)
|
|
el.removeEventListener('lostpointercapture', endDrag)
|
|
cleanupDrag = null
|
|
}
|
|
|
|
cleanupDrag = endDrag
|
|
|
|
el.addEventListener('pointermove', onMove)
|
|
el.addEventListener('pointerup', endDrag)
|
|
el.addEventListener('lostpointercapture', endDrag)
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
cleanupDrag?.()
|
|
})
|
|
|
|
return {
|
|
handleTrackPointerDown,
|
|
startDrag
|
|
}
|
|
}
|