mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-24 08:19:51 +00:00
feat: add gradient-slider widget for FLOAT inputs (#8992)
## Summary Add a new 'gradient-slider' display mode for FLOAT widget inputs. Nodes can specify gradient_stops (color stop arrays) to render a colored gradient track behind the slider thumb, useful for color adjustment parameters like hue, saturation, brightness, etc. - GradientSlider.vue: reusable Reka UI-based gradient slider component - GradientSliderWidget.ts: litegraph canvas-mode fallback rendering - WidgetInputNumberGradientSlider.vue: Vue node widget integration - Schema, registry, and type updates for gradient-slider support this is prerequisite for color correct and balance BE changes https://github.com/Comfy-Org/ComfyUI/pull/12536 ## Screenshots (if applicable) <img width="610" height="237" alt="image" src="https://github.com/user-attachments/assets/b0577ca8-8576-4062-8f14-0a3612e56242" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8992-feat-add-gradient-slider-widget-for-FLOAT-inputs-30d6d73d36508199b3e8db6a0c213ab4) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org>
This commit is contained in:
@@ -6,6 +6,7 @@ import type {
|
||||
SimplifiedWidget
|
||||
} from '@/types/simplifiedWidget'
|
||||
|
||||
import WidgetInputNumberGradientSlider from './WidgetInputNumberGradientSlider.vue'
|
||||
import WidgetInputNumberInput from './WidgetInputNumberInput.vue'
|
||||
import WidgetInputNumberSlider from './WidgetInputNumberSlider.vue'
|
||||
import WidgetWithControl from './WidgetWithControl.vue'
|
||||
@@ -16,28 +17,33 @@ const props = defineProps<{
|
||||
|
||||
const modelValue = defineModel<number>({ default: 0 })
|
||||
|
||||
const hasControlAfterGenerate = computed(() => {
|
||||
return !!props.widget.controlWidget
|
||||
const controlWidget = computed<SimplifiedControlWidget<number> | null>(() =>
|
||||
props.widget.controlWidget
|
||||
? (props.widget as SimplifiedControlWidget<number>)
|
||||
: null
|
||||
)
|
||||
|
||||
const widgetComponent = computed(() => {
|
||||
switch (props.widget.type) {
|
||||
case 'gradientslider':
|
||||
return WidgetInputNumberGradientSlider
|
||||
case 'slider':
|
||||
return WidgetInputNumberSlider
|
||||
default:
|
||||
return WidgetInputNumberInput
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WidgetWithControl
|
||||
v-if="hasControlAfterGenerate"
|
||||
v-if="controlWidget"
|
||||
v-model="modelValue"
|
||||
:widget="widget as SimplifiedControlWidget<number>"
|
||||
:component="
|
||||
widget.type === 'slider'
|
||||
? WidgetInputNumberSlider
|
||||
: WidgetInputNumberInput
|
||||
"
|
||||
:widget="controlWidget"
|
||||
:component="widgetComponent"
|
||||
/>
|
||||
<component
|
||||
:is="
|
||||
widget.type === 'slider'
|
||||
? WidgetInputNumberSlider
|
||||
: WidgetInputNumberInput
|
||||
"
|
||||
:is="widgetComponent"
|
||||
v-else
|
||||
v-model="modelValue"
|
||||
:widget="widget"
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<WidgetLayoutField :widget="widget">
|
||||
<div :class="cn(WidgetInputBaseClass, 'flex items-center gap-2 pl-3 pr-2')">
|
||||
<GradientSlider
|
||||
v-model="modelValue"
|
||||
:stops="gradientStops"
|
||||
:min="widget.options?.min ?? 0"
|
||||
:max="widget.options?.max ?? 100"
|
||||
:step="stepValue"
|
||||
:disabled="widget.options?.disabled"
|
||||
:aria-label="widget.name"
|
||||
class="flex-1 min-w-0"
|
||||
/>
|
||||
<InputNumber
|
||||
:key="timesEmptied"
|
||||
:model-value="modelValue"
|
||||
v-bind="filteredProps"
|
||||
:step="stepValue"
|
||||
:min-fraction-digits="precision"
|
||||
:max-fraction-digits="precision"
|
||||
:aria-label="widget.name"
|
||||
size="small"
|
||||
pt:pc-input-text:root="min-w-[4ch] bg-transparent border-none text-center truncate"
|
||||
class="w-16 shrink-0"
|
||||
:pt="numberPt"
|
||||
@update:model-value="handleNumberInputUpdate"
|
||||
/>
|
||||
</div>
|
||||
</WidgetLayoutField>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import InputNumber from 'primevue/inputnumber'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import GradientSlider from '@/components/gradientslider/GradientSlider.vue'
|
||||
import type { ColorStop } from '@/lib/litegraph/src/interfaces'
|
||||
import type { IWidgetGradientSliderOptions } from '@/lib/litegraph/src/types/widgets'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import {
|
||||
STANDARD_EXCLUDED_PROPS,
|
||||
filterWidgetProps
|
||||
} from '@/utils/widgetPropFilter'
|
||||
|
||||
import { useNumberStepCalculation } from '../composables/useNumberStepCalculation'
|
||||
import { useNumberWidgetButtonPt } from '../composables/useNumberWidgetButtonPt'
|
||||
import { WidgetInputBaseClass } from './layout'
|
||||
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
|
||||
|
||||
const DEFAULT_GRADIENT_STOPS: ColorStop[] = [
|
||||
{ offset: 0, color: [0, 0, 0] },
|
||||
{ offset: 1, color: [255, 255, 255] }
|
||||
]
|
||||
|
||||
const { widget } = defineProps<{
|
||||
widget: SimplifiedWidget<number, IWidgetGradientSliderOptions>
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<number>({ default: 0 })
|
||||
|
||||
const timesEmptied = ref(0)
|
||||
|
||||
const handleNumberInputUpdate = (newValue: number | undefined) => {
|
||||
if (newValue !== undefined) {
|
||||
modelValue.value = newValue
|
||||
return
|
||||
}
|
||||
timesEmptied.value += 1
|
||||
}
|
||||
|
||||
const gradientStops = computed<ColorStop[]>(() => {
|
||||
const stops = widget.options?.gradient_stops
|
||||
if (stops && stops.length >= 2) return stops
|
||||
return DEFAULT_GRADIENT_STOPS
|
||||
})
|
||||
|
||||
const filteredProps = computed(() =>
|
||||
filterWidgetProps(widget.options, STANDARD_EXCLUDED_PROPS)
|
||||
)
|
||||
|
||||
const precision = computed(() => {
|
||||
const p = widget.options?.precision
|
||||
return typeof p === 'number' && p >= 0 ? p : undefined
|
||||
})
|
||||
|
||||
const stepValue = useNumberStepCalculation(widget.options, precision, true)
|
||||
|
||||
const numberPt = useNumberWidgetButtonPt({
|
||||
roundedLeft: true,
|
||||
roundedRight: true
|
||||
})
|
||||
</script>
|
||||
@@ -43,11 +43,13 @@ export const useFloatWidget = () => {
|
||||
|
||||
const display_type = inputSpec.display
|
||||
const widgetType =
|
||||
sliderEnabled && display_type == 'slider'
|
||||
? 'slider'
|
||||
: display_type == 'knob'
|
||||
? 'knob'
|
||||
: 'number'
|
||||
display_type == 'gradientslider'
|
||||
? 'gradientslider'
|
||||
: sliderEnabled && display_type == 'slider'
|
||||
? 'slider'
|
||||
: display_type == 'knob'
|
||||
? 'knob'
|
||||
: 'number'
|
||||
|
||||
const step = inputSpec.step ?? 0.5
|
||||
const precision =
|
||||
@@ -72,7 +74,10 @@ export const useFloatWidget = () => {
|
||||
/** @deprecated Use step2 instead. The 10x value is a legacy implementation. */
|
||||
step: step * 10.0,
|
||||
step2: step,
|
||||
precision
|
||||
precision,
|
||||
...(inputSpec.gradient_stops
|
||||
? { gradient_stops: inputSpec.gradient_stops }
|
||||
: {})
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ const coreWidgetDefinitions: Array<[string, WidgetDefinition]> = [
|
||||
'float',
|
||||
{
|
||||
component: WidgetInputNumber,
|
||||
aliases: ['FLOAT', 'number', 'slider'],
|
||||
aliases: ['FLOAT', 'number', 'slider', 'gradientslider'],
|
||||
essential: true
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user