Skip to content

Commit

Permalink
Merge pull request #6111 from ericmehl/staticColorSliders
Browse files Browse the repository at this point in the history
Static color sliders / field
  • Loading branch information
ericmehl authored Nov 13, 2024
2 parents 60e77a8 + ec79d5b commit 193e1cf
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 35 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Improvements
- DeletePoints : Added modes for deleting points based on a list of ids.
- Light Editor, Attribute Editor, Spreadsheet : Add original and current color swatches to color popups.
- SceneView : Added fallback framing extents to create a reasonable view when `SceneGadget` is empty, for example if the grid is hidden.
- ColorChooser : Added an option to toggle the dynamic update of colors displayed in the slider and color field backgrounds. When enabled, the widget backgrounds update to show the color that will result from moving the indicator to a given position. When disabled, a static range of values is displayed instead.

Fixes
-----
Expand Down
147 changes: 113 additions & 34 deletions python/GafferUI/ColorChooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def _drawIndicator( painter, position ) :
# A custom slider for drawing the backgrounds.
class _ComponentSlider( GafferUI.Slider ) :

def __init__( self, color, component, **kw ) :
def __init__( self, color, component, dynamicBackground = True, **kw ) :

GafferUI.Slider.__init__(
self, 0.0,
Expand All @@ -128,49 +128,79 @@ def __init__( self, color, component, **kw ) :

self.color = color
self.component = component
self.__dynamicBackground = dynamicBackground
self.__gradientToDraw = None
self.__size = self.size()

# Sets the slider color in RGB space for RGBA channels,
# HSV space for HSV channels and TMI space for TMI channels.
def setColor( self, color ) :

if (
( self.__dynamicBackground and color != self.color ) or
( not self.__dynamicBackground and self.component == "s" )
) :
self.__gradientToDraw = None
self.color = color
self._qtWidget().update()

def getColor( self ) :

return self.color

def _drawBackground( self, painter ) :
def setDynamicBackground( self, dynamicBackground ) :

size = self.size()
grad = QtGui.QLinearGradient( 0, 0, size.x, 0 )
self.__dynamicBackground = dynamicBackground
self.__gradientToDraw = None
self._qtWidget().update()

displayTransform = self.displayTransform()
def getDynamicBackground( self ) :

if self.component == "a" :
c1 = imath.Color3f( 0 )
c2 = imath.Color3f( 1 )
else :
c1 = imath.Color3f( self.color[0], self.color[1], self.color[2] )
c2 = imath.Color3f( self.color[0], self.color[1], self.color[2] )
a = { "r" : 0, "g" : 1, "b" : 2, "h" : 0, "s" : 1, "v": 2, "t" : 0, "m" : 1, "i" : 2 }[self.component]
c1[a] = -1 if self.component in "tm" else 0
c2[a] = 1
return self.__dynamicBackground

numStops = max( 2, size.x // 2 )
for i in range( 0, numStops ) :
def _drawBackground( self, painter ) :

size = self.size()

t = float( i ) / (numStops-1)
c = c1 + (c2-c1) * t
if self.component in "hsv" :
c = c.hsv2rgb()
elif self.component in "tmi" :
c = _tmiToRGB( c )
if self.__gradientToDraw is None or size != self.__size :
self.__gradientToDraw = QtGui.QLinearGradient( 0, 0, size.x, 0 )

grad.setColorAt( t, self._qtColor( displayTransform( c ) ) )
displayTransform = self.displayTransform()

brush = QtGui.QBrush( grad )
if self.component == "a" :
c1 = imath.Color3f( 0 )
c2 = imath.Color3f( 1 )
else :
if self.__dynamicBackground :
c1 = imath.Color3f( self.color[0], self.color[1], self.color[2] )
elif self.component in "rgbvi" :
c1 = imath.Color3f( 0 )
elif self.component == "h" :
c1 = imath.Color3f( 0, 1, 1 )
elif self.component == "s" :
c1 = imath.Color3f( self.color[0], 0, 1 )
elif self.component in "tm" :
c1 = imath.Color3f( 0, 0, 0.5 )
c2 = imath.Color3f( c1 )
a = { "r" : 0, "g" : 1, "b" : 2, "h" : 0, "s" : 1, "v": 2, "t" : 0, "m" : 1, "i" : 2 }[self.component]
c1[a] = -1 if self.component in "tm" else 0
c2[a] = 1

numStops = max( 2, size.x // 2 )
for i in range( 0, numStops ) :

t = float( i ) / (numStops-1)
c = c1 + (c2-c1) * t
if self.component in "hsv" :
c = c.hsv2rgb()
elif self.component in "tmi" :
c = _tmiToRGB( c )

self.__gradientToDraw.setColorAt( t, self._qtColor( displayTransform( c ) ) )

brush = QtGui.QBrush( self.__gradientToDraw )
painter.fillRect( 0, 0, size.x, size.y, brush )
self.__size = size

def _drawValue( self, painter, value, position, state ) :

Expand Down Expand Up @@ -209,11 +239,12 @@ def _drawValue( self, painter, value, position, state ) :
def _displayTransformChanged( self ) :

GafferUI.Slider._displayTransformChanged( self )
self.__gradientToDraw = None
self._qtWidget().update()

class _ColorField( GafferUI.Widget ) :

def __init__( self, color = imath.Color3f( 1.0 ), staticComponent = "v", **kw ) :
def __init__( self, color = imath.Color3f( 1.0 ), staticComponent = "v", dynamicBackground = True, **kw ) :

GafferUI.Widget.__init__( self, QtWidgets.QWidget(), **kw )

Expand All @@ -232,6 +263,8 @@ def __init__( self, color = imath.Color3f( 1.0 ), staticComponent = "v", **kw )
self.__colorFieldToDraw = None
self.setColor( color, staticComponent )

self.setDynamicBackground( dynamicBackground )

# Sets the color and the static component. `color` is in
# RGB space for RGB static components, HSV space for
# HSV static components and TMI space for TMI components.
Expand All @@ -244,6 +277,16 @@ def getColor( self ) :

return self.__color, self.__staticComponent

def setDynamicBackground( self, dynamicBackground ) :

self.__dynamicBackground = dynamicBackground
self.__colorFieldToDraw = None
self._qtWidget().update()

def getDynamicBackground( self ) :

return self.__dynamicBackground

## A signal emitted whenever a value has been changed. Slots should
# have the signature slot( _ColorField, GafferUI.Slider.ValueChangedReason )
def valueChangedSignal( self ) :
Expand Down Expand Up @@ -271,7 +314,11 @@ def __setColorInternal( self, color, staticComponent, reason ) :
return

zIndex = self.__zIndex()
if color[zIndex] != self.__color[zIndex] or staticComponent != self.__staticComponent :
if (
staticComponent != self.__staticComponent or
( self.__dynamicBackground and color[zIndex] != self.__color[zIndex] ) or
( not self.__dynamicBackground and staticComponent == "h" )
) :
self.__colorFieldToDraw = None

self.__color = color
Expand Down Expand Up @@ -430,10 +477,15 @@ def __drawBackground( self, painter ) :
xIndex, yIndex = self.__xyIndices()
zIndex = self.__zIndex()

staticValue = self.__color[zIndex]

c = imath.Color3f()
c[zIndex] = staticValue
if self.__dynamicBackground or self.__staticComponent == "h" :
c[zIndex] = self.__color[zIndex]
elif self.__staticComponent in "rgbtm" :
c[zIndex] = 0.0
elif self.__staticComponent in "sv" :
c[zIndex] = 1.0
elif self.__staticComponent == "i" :
c[zIndex] = 0.5

ColorSpace = enum.Enum( "ColorSpace", [ "RGB", "HSV", "TMI" ] )
if self.__staticComponent in "rgb" :
Expand Down Expand Up @@ -835,6 +887,7 @@ def __init__( self, color=imath.Color3f( 1 ), **kw ) :
self.__visibleComponentsChangedSignal = Gaffer.Signals.Signal1()
self.__staticComponentChangedSignal = Gaffer.Signals.Signal1()
self.__colorFieldVisibleChangedSignal = Gaffer.Signals.Signal1()
self.__dynamicSliderBackgroundsChangedSignal = Gaffer.Signals.Signal1()
self.__optionsMenuSignal = Gaffer.Signals.Signal2()

self.__colorFieldPrimaryIcon = GafferUI.Image( "colorFieldPrimaryIcon.png" )
Expand Down Expand Up @@ -920,6 +973,18 @@ def getColorFieldVisible( self ) :

return self.__colorField.getVisible()

def setDynamicSliderBackgrounds( self, dynamic ) :

for component, slider in self.__sliders.items() :
slider.setDynamicBackground( dynamic )
self.__colorField.setDynamicBackground( dynamic )

self.__dynamicSliderBackgroundsChangedSignal( self )

def getDynamicSliderBackgrounds( self ) :

return self.__sliders["r"].getDynamicBackground()

## A signal emitted whenever the color is changed. Slots should
# have the signature slot( ColorChooser, reason ). The reason
# argument may be passed either a ColorChooser.ColorChangedReason,
Expand All @@ -931,27 +996,28 @@ def colorChangedSignal( self ) :

## A signal emitted whenever the visible components are changed. Slots
# should have the signature slot( ColorChooser ).
# `visibleComponents` is a string representing the components currently
# visible.
def visibleComponentsChangedSignal( self ) :

return self.__visibleComponentsChangedSignal

## A signal emitted whenever the static component is changed. Slots
# should have the signature slot( ColorChooser ).
# `staticComponent` is a single character string representing the
# current static component.
def staticComponentChangedSignal( self ) :

return self.__staticComponentChangedSignal

## A signal emitted whenever the visibility of the color field changes.
# Slots should have the signature slot( ColorChooser ).
# `visible` is a boolean representing the current visibility.
def colorFieldVisibleChangedSignal( self ) :

return self.__colorFieldVisibleChangedSignal

## A signal emitted whenever the dynamic colors option is changed.
# Slots should have the signature slot( ColorChooser ).
def dynamicSliderBackgroundsChangedSignal( self ) :

return self.__dynamicSliderBackgroundsChangedSignal

## A signal emitted whenever the options menu is opened.
# Slots should have the signature slot( ColorChooser, menuDefinition )
# and add menu items to `menuDefinition`.
Expand Down Expand Up @@ -1018,6 +1084,19 @@ def __optionsMenuDefinition( self ) :
}
)

result.append( "/__sliders__", { "divider": True, "label": "Sliders" } )

result.append(
"/Dynamic Backgrounds",
{
"command": Gaffer.WeakMethod( self.setDynamicSliderBackgrounds ),
"checkBox": self.getDynamicSliderBackgrounds(),
"description": """With Dynamic Backgrounds enabled, the backgrounds of the color sliders will
update to show the color that will result from moving the indicator to a given
position. When disabled, a static range of values is displayed."""
}
)

self.__optionsMenuSignal( self, result )

return result
Expand Down
16 changes: 15 additions & 1 deletion python/GafferUI/ColorChooserPlugValueWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ def __init__( self, plugs, **kw ) :
if colorFieldVisible is not None :
self.__colorChooser.setColorFieldVisible( colorFieldVisible )

dynamicSliderBackgrounds = self.__colorChooserOption( "dynamicSliderBackgrounds" )
if dynamicSliderBackgrounds is not None :
self.__colorChooser.setDynamicSliderBackgrounds( dynamicSliderBackgrounds )

self.__colorChangedConnection = self.__colorChooser.colorChangedSignal().connect(
Gaffer.WeakMethod( self.__colorChanged )
)
Expand All @@ -78,6 +82,9 @@ def __init__( self, plugs, **kw ) :
self.__colorChooser.colorFieldVisibleChangedSignal().connect(
functools.partial( Gaffer.WeakMethod( self.__colorChooserColorFieldVisibleChanged ) )
)
self.__colorChooser.dynamicSliderBackgroundsChangedSignal().connect(
functools.partial( Gaffer.WeakMethod( self.__dynamicSliderBackgroundsChanged ) )
)
self.__colorChooser.optionsMenuSignal().connect(
functools.partial( Gaffer.WeakMethod( self.__colorChooserOptionsMenu ) ),
scoped = False
Expand Down Expand Up @@ -157,6 +164,10 @@ def __colorChooserColorFieldVisibleChanged( self, colorChooser ) :

self.__colorChooserOptionChanged( "colorFieldVisible", colorChooser.getColorFieldVisible() )

def __dynamicSliderBackgroundsChanged( self, colorChooser ) :

self.__colorChooserOptionChanged( "dynamicSliderBackgrounds", colorChooser.getDynamicSliderBackgrounds() )

def __colorChooserOptionsMenu( self, colorChooser, menuDefinition ) :

menuDefinition.append( "/__saveDefaultOptions__", { "divider": True, "label": "Defaults" } )
Expand Down Expand Up @@ -193,14 +204,16 @@ def saveDefaultOptions( colorChooser, keyPrefix, scriptPath = None ) :
visibleComponents = colorChooser.getVisibleComponents()
staticComponent = colorChooser.getColorFieldStaticComponent()
colorFieldVisible = colorChooser.getColorFieldVisible()
dynamicSliderBackgrounds = colorChooser.getDynamicSliderBackgrounds()

for p in [ Gaffer.Color3fPlug, Gaffer.Color4fPlug ] :
for k in [ "visibleComponents", "staticComponent", "colorFieldVisible" ] :
for k in [ "visibleComponents", "staticComponent", "colorFieldVisible", "dynamicSliderBackgrounds" ] :
Gaffer.Metadata.deregisterValue( p, keyPrefix + k )

Gaffer.Metadata.registerValue( p, keyPrefix + "visibleComponents", visibleComponents )
Gaffer.Metadata.registerValue( p, keyPrefix + "staticComponent", staticComponent )
Gaffer.Metadata.registerValue( p, keyPrefix + "colorFieldVisible", colorFieldVisible )
Gaffer.Metadata.registerValue( p, keyPrefix + "dynamicSliderBackgrounds", dynamicSliderBackgrounds )

if scriptPath is None :
return
Expand All @@ -226,6 +239,7 @@ def saveDefaultOptions( colorChooser, keyPrefix, scriptPath = None ) :
newScript.append( f"Gaffer.Metadata.registerValue( Gaffer.Color{c}fPlug, \"{keyPrefix}visibleComponents\", \"{visibleComponents}\" )\n" )
newScript.append( f"Gaffer.Metadata.registerValue( Gaffer.Color{c}fPlug, \"{keyPrefix}staticComponent\", \"{staticComponent}\" )\n" )
newScript.append( f"Gaffer.Metadata.registerValue( Gaffer.Color{c}fPlug, \"{keyPrefix}colorFieldVisible\", {colorFieldVisible} )\n" )
newScript.append( f"Gaffer.Metadata.registerValue( Gaffer.Color{c}fPlug, \"{keyPrefix}dynamicSliderBackgrounds\", {dynamicSliderBackgrounds} )\n" )

with open( scriptPath, "w" ) as outFile :
outFile.writelines( newScript )
11 changes: 11 additions & 0 deletions python/GafferUI/ColorSwatchPlugValueWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ def __init__( self, plugs, parentWindow ) :
self.colorChooser().colorFieldVisibleChangedSignal().connect(
functools.partial( Gaffer.WeakMethod( self.__colorChooserColorFieldVisibleChanged ) )
)
self.colorChooser().dynamicSliderBackgroundsChangedSignal().connect(
functools.partial( Gaffer.WeakMethod( self.__dynamicSliderBackgroundsChanged ) )
)
self.colorChooser().optionsMenuSignal().connect(
functools.partial( Gaffer.WeakMethod( self.__colorChooserOptionsMenu ) ),
scoped = False
Expand Down Expand Up @@ -183,6 +186,10 @@ def __init__( self, plugs, parentWindow ) :
if colorFieldVisible is not None :
self.colorChooser().setColorFieldVisible( colorFieldVisible )

dynamicSliderBackgrounds = self.__colorChooserOption( "dynamicSliderBackgrounds" )
if dynamicSliderBackgrounds is not None :
self.colorChooser().setDynamicSliderBackgrounds( dynamicSliderBackgrounds )

parentWindow.addChildWindow( self, removeOnClose = True )

@classmethod
Expand Down Expand Up @@ -287,3 +294,7 @@ def __colorChooserStaticComponentChanged( self, colorChooser ) :
def __colorChooserColorFieldVisibleChanged( self, colorChooser ) :

self.__colorChooserOptionChanged( "colorFieldVisible", colorChooser.getColorFieldVisible() )

def __dynamicSliderBackgroundsChanged( self, colorChooser ) :

self.__colorChooserOptionChanged( "dynamicSliderBackgrounds", colorChooser.getDynamicSliderBackgrounds() )
Loading

0 comments on commit 193e1cf

Please sign in to comment.