NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

color_picker.py (25142B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\carbonui\control\color_picker.py
      2 from __future__ import division
      3 import math
      4 import carbonui
      5 import localization
      6 import mathext
      7 import signals
      8 from carbonui import Align, Axis, Density, uiconst, PickState
      9 from carbonui.button import styling
     10 from carbonui.control.forms.formFields import IntegerEditField
     11 from carbonui.control.singlelineedits.singleLineEditInteger import SingleLineEditInteger
     12 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText
     13 from carbonui.decorative.inputUnderlay import InputUnderlay
     14 from carbonui.primitives.container import Container
     15 from carbonui.primitives.fill import Fill
     16 from carbonui.primitives.gradientSprite import GradientSprite
     17 from carbonui.primitives.sprite import Sprite
     18 from carbonui.uicore import uicore
     19 from chroma import Color
     20 from eve.client.script.ui import eveColor
     21 from eve.client.script.ui.control.tooltips import TooltipPersistentPanel
     22 DEFAULT_COLOR = carbonui.Color.from_hex('#000000')
     23 
     24 class ColorFill(Container):
     25     default_clipChildren = True
     26 
     27     def __init__(self, color = DEFAULT_COLOR, on_color_changed = None, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), ignore_color_blind_mode = False, **kwargs):
     28         self._color = color
     29         self._brightness_range = brightness_range
     30         self._saturation_range = saturation_range
     31         self._on_color_changed = signals.Signal()
     32         self._ignore_color_blind_mode = ignore_color_blind_mode
     33         self._on_color_changed = signals.Signal()
     34         super(ColorFill, self).__init__(**kwargs)
     35         self._layout()
     36         self._update_color_fill()
     37         if on_color_changed:
     38             self._on_color_changed.connect(on_color_changed)
     39 
     40     @property
     41     def color(self):
     42         return self._color
     43 
     44     @color.setter
     45     def color(self, value):
     46         value = carbonui.Color.from_any(value) if value else None
     47         if self._color != value:
     48             self._color = value
     49             self._update_color_fill()
     50 
     51     def _layout(self):
     52         self._color_fill = Fill(name='color_fill', parent=self, padding=1, color=self._color, ignoreColorBlindMode=self._ignore_color_blind_mode, hint=localization.GetByLabel('UI/SystemMenu/GeneralSettings/ColorTheme/ColorPickerEditColorHint'))
     53         InputUnderlay(bgParent=self)
     54 
     55     def OnClick(self, *args):
     56         self.show_popover()
     57 
     58     def show_popover(self):
     59         show_color_picker_popover(owner=self._color_fill, color=self.color or DEFAULT_COLOR, on_color_changed=self._on_popover_picker_color_changed, brightness_range=self._brightness_range, saturation_range=self._saturation_range, ignore_color_blind_mode=self._ignore_color_blind_mode)
     60 
     61     def _on_popover_picker_color_changed(self, picker):
     62         self.color = picker.color
     63         self._on_color_changed(self)
     64 
     65     def _update_color_fill(self):
     66         if self._color_fill:
     67             color = self.color or DEFAULT_COLOR
     68             self._color_fill.SetRGB(*color.rgb)
     69 
     70 
     71 class ColorHexInputField(SingleLineEditText):
     72     _color_cont = None
     73     default_displayHistory = False
     74 
     75     def __init__(self, color = DEFAULT_COLOR, density = Density.NORMAL, width = 160, on_color_changed = None, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), ignore_color_blind_mode = False, **kwargs):
     76         self._color = color
     77         self._brightness_range = brightness_range
     78         self._saturation_range = saturation_range
     79         self._density = density
     80         self._on_color_changed = signals.Signal()
     81         self._ignore_color_blind_mode = ignore_color_blind_mode
     82         kwargs['setvalue'] = (self._get_color_text(),)
     83         kwargs['OnChange'] = self.on_text_field_change
     84         kwargs['OnFocusLost'] = self._on_text_field_focus_lost
     85         super(ColorHexInputField, self).__init__(width=width, height=self._get_height(), **kwargs)
     86         self._update_field_text()
     87         if on_color_changed:
     88             self._on_color_changed.connect(on_color_changed)
     89 
     90     @property
     91     def color(self):
     92         return self._color
     93 
     94     @color.setter
     95     def color(self, value):
     96         value = carbonui.Color.from_any(value) if value else None
     97         if self._color != value:
     98             self._color = value
     99             self._update_field_text()
    100 
    101     def _get_color_text(self):
    102         if self._color:
    103             return self._color.hex_rgb.upper()
    104         return '#'
    105 
    106     def _get_height(self):
    107         return styling.get_height(self._density)
    108 
    109     def _update_field_text(self):
    110         focused = uicore.registry.GetFocus() == self
    111         if not focused:
    112             self.SetValue(self._get_color_text(), docallback=False)
    113 
    114     def _on_text_field_focus_lost(self, input):
    115         try:
    116             color = carbonui.Color.from_hex(self.GetValue().strip())
    117             color = color.with_capped_brightness(*self._brightness_range)
    118         except Exception:
    119             pass
    120         else:
    121             self.color = color
    122             self._on_color_changed(self)
    123 
    124         self._update_field_text()
    125 
    126     def on_text_field_change(self, text):
    127         try:
    128             color = carbonui.Color.from_hex(self.GetValue().strip())
    129             color = color.with_capped_brightness(*self._brightness_range)
    130         except Exception:
    131             pass
    132         else:
    133             self.color = color
    134             self._on_color_changed(self)
    135 
    136     def Confirm(self, *args):
    137         uicore.registry.SetFocus(None)
    138 
    139 
    140 class ColorField(Container):
    141     _color_fill = None
    142     _color_cont = None
    143 
    144     def __init__(self, color = DEFAULT_COLOR, density = Density.NORMAL, width = 160, on_color_changed = None, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), ignore_color_blind_mode = False, **kwargs):
    145         self._color = color
    146         self._brightness_range = brightness_range
    147         self._saturation_range = saturation_range
    148         self._density = density
    149         self._on_color_changed = signals.Signal()
    150         self._ignore_color_blind_mode = ignore_color_blind_mode
    151         super(ColorField, self).__init__(width=width, height=self._get_height(), **kwargs)
    152         self._layout()
    153         if on_color_changed:
    154             self._on_color_changed.connect(on_color_changed)
    155 
    156     @property
    157     def color(self):
    158         return self._color
    159 
    160     @color.setter
    161     def color(self, value):
    162         value = carbonui.Color.from_any(value) if value else None
    163         if self._color != value:
    164             self._color = value
    165             self._color_fill.color = value
    166             self._hex_input_field.color = value
    167 
    168     def _layout(self):
    169         self._color_fill = ColorFill(parent=self, align=Align.TOLEFT, pickState=PickState.ON, width=self._get_height(), color=self.color, brightness_range=self._brightness_range, saturation_range=self._saturation_range, ignore_color_blind_mode=self._ignore_color_blind_mode, on_color_changed=self.on_color_fill_color_changed, padRight=1)
    170         self._hex_input_field = ColorHexInputField(parent=self, align=Align.TOTOP, autoselect=True, on_color_changed=self.on_text_field_color_change)
    171 
    172     def on_color_fill_color_changed(self, color_fill):
    173         self.color = color_fill.color
    174         self._on_color_changed(self)
    175 
    176     def _get_height(self):
    177         return styling.get_height(self._density)
    178 
    179     def on_text_field_color_change(self, text_field):
    180         self.color = self._hex_input_field.color
    181         self._on_color_changed(self)
    182 
    183     def show_popover(self):
    184         self._color_fill.show_popover()
    185 
    186 
    187 class _ColorPickerPopoverArguments(object):
    188 
    189     def __init__(self, color, on_color_changed = None, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), show_text_fields = False, ignore_color_blind_mode = False):
    190         self.brightness_range = brightness_range
    191         self.saturation_range = saturation_range
    192         self.color = color
    193         self.on_color_changed = on_color_changed
    194         self.show_text_fields = show_text_fields
    195         self.ignore_color_blind_mode = ignore_color_blind_mode
    196 
    197 
    198 def show_color_picker_popover(owner, color, on_color_changed = None, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), show_text_fields = False, ignore_color_blind_mode = False):
    199     arguments = _ColorPickerPopoverArguments(color=color, on_color_changed=on_color_changed, brightness_range=brightness_range, saturation_range=saturation_range, show_text_fields=show_text_fields, ignore_color_blind_mode=ignore_color_blind_mode)
    200     return uicore.uilib.tooltipHandler.LoadPersistentTooltip(owner=owner, customTooltipClass=_ColorFieldPickerPopover, loadArguments=(arguments,), parent=uicore.layer.hint)
    201 
    202 
    203 class _ColorFieldPickerPopover(TooltipPersistentPanel):
    204     default_pointerDirection = uiconst.POINT_BOTTOM_2
    205     default_columns = 1
    206     default_state = uiconst.UI_NORMAL
    207     default_margin = 16
    208     isTopLevelWindow = True
    209     _picker = None
    210 
    211     def __init__(self, **kwargs):
    212         self.on_color_changed = signals.Signal()
    213         super(_ColorFieldPickerPopover, self).__init__(**kwargs)
    214         self.pickState = uiconst.TR2_SPS_ON
    215         uicore.registry.SetFocus(self)
    216         uicore.uilib.RegisterForTriuiEvents(uiconst.UI_MOUSEDOWN, self._on_global_mouse_down)
    217 
    218     @property
    219     def color(self):
    220         return self._picker.color
    221 
    222     def LoadTooltip(self, arguments):
    223         ColorPicker(parent=self, align=Align.TOPLEFT, color=arguments.color, on_color_changed=arguments.on_color_changed, brightness_range=arguments.brightness_range, saturation_range=arguments.saturation_range, show_text_fields=arguments.show_text_fields, ignore_color_blind_mode=arguments.ignore_color_blind_mode)
    224 
    225     def _on_global_mouse_down(self, *args):
    226         if uicore.uilib.mouseOver is self or uicore.uilib.mouseOver.IsUnder(self):
    227             return True
    228         else:
    229             self.Close()
    230             return False
    231 
    232 
    233 class ColorTextInputFields(Container):
    234     default_height = 32
    235 
    236     def __init__(self, color = carbonui.Color.from_hsb(h=0.0, s=0.0, b=1.0), on_color_changed = None, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), ignore_color_blind_mode = False, **kwargs):
    237         super(ColorTextInputFields, self).__init__(**kwargs)
    238         self._color = color
    239         self.on_color_changed = signals.Signal()
    240         self._construct_rgb_fields()
    241         self._hex_input_field = ColorHexInputField(parent=self, color=color, align=Align.TORIGHT, width=80, brightness_range=brightness_range, saturation_range=saturation_range, ignore_color_blind_mode=ignore_color_blind_mode, on_color_changed=self.on_hex_input_field_color_changed)
    242         self._update_rgb_input_fields()
    243         if on_color_changed:
    244             self.on_color_changed.connect(on_color_changed)
    245 
    246     def _construct_rgb_fields(self):
    247         self._r_input_field = SingleLineEditInteger(parent=self, align=Align.TOLEFT, width=48, minValue=0, maxValue=255, showNumericControls=False, OnChange=self.on_rgb_field_change, OnFocusLost=self.on_rgb_field_change)
    248         self._g_input_field = SingleLineEditInteger(parent=self, align=Align.TOLEFT, width=48, minValue=0, maxValue=255, showNumericControls=False, OnChange=self.on_rgb_field_change, OnFocusLost=self.on_rgb_field_change)
    249         self._b_input_field = SingleLineEditInteger(parent=self, align=Align.TOLEFT, width=48, minValue=0, maxValue=255, showNumericControls=False, OnChange=self.on_rgb_field_change, OnFocusLost=self.on_rgb_field_change)
    250 
    251     def on_rgb_field_change(self, *args):
    252         self.color = Color.from_rgba_8bit(self._r_input_field.GetValue(), self._g_input_field.GetValue(), self._b_input_field.GetValue())
    253         self.on_color_changed(self)
    254 
    255     def on_hex_input_field_color_changed(self, *args):
    256         self.color = self._hex_input_field.color
    257         self.on_color_changed(self)
    258 
    259     @property
    260     def color(self):
    261         return self._color
    262 
    263     @color.setter
    264     def color(self, value):
    265         if self._color != value:
    266             self._color = value
    267             self._hex_input_field.color = value
    268             self._update_rgb_input_fields()
    269 
    270     def _update_rgb_input_fields(self):
    271         r, g, b = self.color.rgb_8bit
    272         self._r_input_field.SetValue(r, docallback=False)
    273         self._g_input_field.SetValue(g, docallback=False)
    274         self._b_input_field.SetValue(b, docallback=False)
    275 
    276 
    277 class ColorPicker(Container):
    278     isTabStop = True
    279 
    280     def __init__(self, width = 236, height = 200, color = carbonui.Color.from_hsb(h=0.0, s=0.0, b=1.0), on_color_changed = None, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), show_text_fields = False, ignore_color_blind_mode = False, **kwargs):
    281         self._hue = color.hue
    282         self._saturation = color.saturation
    283         self._brightness = color.brightness
    284         self.on_color_changed = signals.Signal()
    285         super(ColorPicker, self).__init__(width=width, height=height, **kwargs)
    286         if show_text_fields:
    287             self._text_input_fields = ColorTextInputFields(parent=self, align=Align.TOBOTTOM, padTop=4, on_color_changed=self.on_text_input_fields_color_changed, color=self.color)
    288             self.height += 36
    289         else:
    290             self._text_input_fields = None
    291         self._hue_slider = HueSlider(parent=self, align=Align.TORIGHT, width=20, padLeft=16, hue=self._hue, on_hue_changed=self._on_hue_changed, ignore_color_blind_mode=ignore_color_blind_mode)
    292         self._picker = Picker2D(parent=self, align=Align.TOALL, position=(self._saturation, 1.0 - self._brightness), on_position_changed=self._on_picker_position_changed, brightness_range=brightness_range, saturation_range=saturation_range, ignore_color_blind_mode=ignore_color_blind_mode)
    293         self._gradient = ColorSaturationBrightnessGradient(parent=self, align=Align.TOALL, hue=self._hue, ignore_color_blind_mode=ignore_color_blind_mode)
    294         if on_color_changed is not None:
    295             self.on_color_changed.connect(on_color_changed)
    296 
    297     def on_text_input_fields_color_changed(self, *args):
    298         self.color = self._text_input_fields.color
    299         self._hue_slider.hue = self._hue
    300 
    301     @property
    302     def color(self):
    303         return carbonui.Color.from_hsb(h=self._hue, s=self._saturation, b=self._brightness)
    304 
    305     @color.setter
    306     def color(self, value):
    307         if self.color != value:
    308             self._hue = value.hue
    309             self._saturation = value.saturation
    310             self._brightness = value.brightness
    311             self._update_color()
    312             self.on_color_changed(self)
    313 
    314     def _update_color(self):
    315         self._gradient.hue = self._hue
    316         if self._text_input_fields:
    317             self._text_input_fields.color = self.color
    318         self._picker.position = (self._saturation, 1.0 - self._brightness)
    319 
    320     def _on_hue_changed(self, picker):
    321         self._hue = picker.hue
    322         self._update_color()
    323         self.on_color_changed(self)
    324 
    325     def _on_picker_position_changed(self, picker):
    326         x, y = picker.position
    327         saturation = x
    328         brightness = 1.0 - y
    329         if not mathext.is_close(saturation, self._saturation) or not mathext.is_close(brightness, self._brightness):
    330             self._saturation = saturation
    331             self._brightness = brightness
    332             self._update_color()
    333             self.on_color_changed(self)
    334 
    335 
    336 class HueSlider(Container):
    337     _dragging = False
    338     _on_hue_changed = None
    339     _STEPS = 10
    340     _INDICATOR_THICKNESS = 2
    341 
    342     def __init__(self, hue = 0.0, orientation = Axis.VERTICAL, enabled = True, on_hue_changed = None, state = uiconst.UI_NORMAL, ignore_color_blind_mode = False, **kwargs):
    343         self._enabled = enabled
    344         self._hue = hue
    345         self._orientation = orientation
    346         super(HueSlider, self).__init__(state=state, **kwargs)
    347         self._indicator = Fill(parent=self, align=Align.TOTOP_NOPUSH if self._orientation == Axis.VERTICAL else Align.TOLEFT_NOPUSH, pos=self._get_indicator_position(), color=carbonui.Color.from_rgba(1.0, 1.0, 1.0, 1.0))
    348         GradientSprite(parent=self, align=Align.TOALL, state=uiconst.UI_DISABLED, rgbData=[ (float(i) / self._STEPS, carbonui.Color.from_hsb(h=float(i) / self._STEPS, s=1.0, b=1.0).rgb) for i in range(self._STEPS + 1) ], rotation=0.0 if self._orientation == Axis.HORIZONTAL else -math.pi / 2.0, ignoreColorBlindMode=ignore_color_blind_mode)
    349         if on_hue_changed:
    350             self.on_hue_changed.connect(on_hue_changed)
    351 
    352     @property
    353     def enabled(self):
    354         return self._enabled
    355 
    356     @enabled.setter
    357     def enabled(self, value):
    358         if self._enabled != value:
    359             self._enabled = bool(value)
    360             if self._dragging:
    361                 self._stop_dragging()
    362 
    363     @property
    364     def hue(self):
    365         return self._hue
    366 
    367     @hue.setter
    368     def hue(self, value):
    369         value = mathext.clamp(float(value), 0.0, 1.0)
    370         if not mathext.is_close(self._hue, value):
    371             self._hue = value
    372             self._update_indicator_position()
    373             if self._on_hue_changed is not None:
    374                 self._on_hue_changed(self)
    375 
    376     @property
    377     def on_hue_changed(self):
    378         if self._on_hue_changed is None:
    379             self._on_hue_changed = signals.Signal('{}.on_hue_changed'.format(self.__class__.__name__))
    380         return self._on_hue_changed
    381 
    382     def _get_indicator_position(self):
    383         width, height = self.GetCurrentAbsoluteSize()
    384         if self._orientation == Axis.VERTICAL:
    385             return (0,
    386              self._hue * (height - self._INDICATOR_THICKNESS),
    387              0,
    388              self._INDICATOR_THICKNESS)
    389         else:
    390             return (self._hue * (width - self._INDICATOR_THICKNESS),
    391              0,
    392              self._INDICATOR_THICKNESS,
    393              0)
    394 
    395     def _update_indicator_position(self):
    396         if self._indicator:
    397             self._indicator.pos = self._get_indicator_position()
    398 
    399     def _stop_dragging(self):
    400         if self._dragging:
    401             self._dragging = False
    402 
    403     def _pick_at_mouse(self):
    404         left, top = self.GetCurrentAbsolutePosition()
    405         width, height = self.GetCurrentAbsoluteSize()
    406         if self._orientation == Axis.HORIZONTAL:
    407             self.hue = mathext.clamp((uicore.uilib.x - left) / width, 0.0, 1.0)
    408         else:
    409             self.hue = mathext.clamp((uicore.uilib.y - top) / height, 0.0, 1.0)
    410 
    411     def OnMouseDown(self, button, *args):
    412         if not self._dragging and button == uiconst.MOUSELEFT and self.enabled:
    413             self._dragging = True
    414             self._pick_at_mouse()
    415 
    416     def OnMouseMove(self, *args):
    417         if self._dragging:
    418             self._pick_at_mouse()
    419 
    420     def OnMouseUp(self, button, *args):
    421         if self._dragging and button == uiconst.MOUSELEFT:
    422             self._pick_at_mouse()
    423             self._stop_dragging()
    424 
    425     def UpdateAlignment(self, budgetLeft = 0, budgetTop = 0, budgetWidth = 0, budgetHeight = 0, updateChildrenOnly = False):
    426         result = super(HueSlider, self).UpdateAlignment(budgetLeft, budgetTop, budgetWidth, budgetHeight, updateChildrenOnly)
    427         _, _, _, _, size_changed = result
    428         if size_changed:
    429             self._update_indicator_position()
    430         return result
    431 
    432 
    433 class _HatchesSprite(Sprite):
    434     default_texturePath = 'res:/UI/Texture/Classes/Industry/Output/hatchPattern.png'
    435     default_tileX = True
    436     default_tileY = True
    437     default_state = uiconst.UI_DISABLED
    438     default_color = eveColor.BLACK
    439     default_opacity = 0.5
    440 
    441 
    442 class Picker2D(Container):
    443     _dragging = False
    444     _on_position_changed = None
    445 
    446     def __init__(self, position = (0.0, 0.0), indicator = None, indicator_hotspot = (0.5, 0.5), enabled = True, on_position_changed = None, state = uiconst.UI_NORMAL, cursor = uiconst.UICORSOR_FINGER, brightness_range = (0.0, 1.0), saturation_range = (0.0, 1.0), **kwargs):
    447         self._enabled = enabled
    448         self._position = position
    449         self._indicator = indicator
    450         self._indicator_hotspot = indicator_hotspot
    451         self._brightness_range = brightness_range
    452         self._saturation_range = saturation_range
    453         super(Picker2D, self).__init__(state=state, cursor=cursor, **kwargs)
    454         if self._indicator is None:
    455             self._indicator = CircleIndicator()
    456         self._indicator.align = Align.TOPLEFT
    457         self._indicator.SetParent(self)
    458         self._check_construct_limitation_hatches()
    459         if on_position_changed:
    460             self.on_position_changed.connect(on_position_changed)
    461 
    462     def _check_construct_limitation_hatches(self):
    463         x_min, x_max = self._saturation_range
    464         y_min, y_max = 1.0 - self._brightness_range[1], 1.0 - self._brightness_range[0]
    465         hatchesCont = Container(name='hatcesCont', parent=self, state=uiconst.UI_DISABLED)
    466         if x_min != 0:
    467             _HatchesSprite(parent=hatchesCont, align=uiconst.TOLEFT_PROP, width=x_min)
    468         if x_max != 1.0:
    469             _HatchesSprite(parent=hatchesCont, align=uiconst.TORIGHT_PROP, width=1.0 - x_max)
    470         if y_min != 0:
    471             _HatchesSprite(parent=hatchesCont, align=uiconst.TOTOP_PROP, height=y_min)
    472         if y_max != 1.0:
    473             _HatchesSprite(parent=hatchesCont, align=uiconst.TOBOTTOM_PROP, height=1.0 - y_max)
    474 
    475     @property
    476     def enabled(self):
    477         return self._enabled
    478 
    479     @enabled.setter
    480     def enabled(self, value):
    481         if self._enabled != value:
    482             self._enabled = value
    483             if self._dragging:
    484                 self._stop_dragging()
    485 
    486     @property
    487     def position(self):
    488         return self._position
    489 
    490     @position.setter
    491     def position(self, value):
    492         if self._position != value:
    493             self._position = value
    494             self._update_indicator_position()
    495 
    496     @property
    497     def on_position_changed(self):
    498         if self._on_position_changed is None:
    499             self._on_position_changed = signals.Signal('{}.on_position_changed'.format(self.__class__.__name__))
    500         return self._on_position_changed
    501 
    502     def _update_indicator_position(self):
    503         width, height = self.GetCurrentAbsoluteSize()
    504         x, y = self._position
    505         offset_x, offset_y = self._get_indicator_offset()
    506         self._indicator.left = math.floor(width * x + offset_x)
    507         self._indicator.top = math.floor(height * y + offset_y)
    508 
    509     def _get_indicator_offset(self):
    510         width, height = self._indicator.GetCurrentAbsoluteSize()
    511         hotspot_x, hotspot_y = self._indicator_hotspot
    512         return (width * -hotspot_x, height * -hotspot_y)
    513 
    514     def _stop_dragging(self):
    515         if self._dragging:
    516             self._dragging = False
    517 
    518     def _update_selected_position(self):
    519         left, top = self.GetCurrentAbsolutePosition()
    520         width, height = self.GetCurrentAbsoluteSize()
    521         sat_min, sat_max = self._saturation_range
    522         bri_min, bri_max = self._brightness_range
    523         x = mathext.clamp((uicore.uilib.x - left) / width, sat_min, sat_max)
    524         y = mathext.clamp((uicore.uilib.y - top) / height, 1.0 - bri_max, 1.0 - bri_min)
    525         self.position = (x, y)
    526         if self._on_position_changed is not None:
    527             self._on_position_changed(self)
    528 
    529     def OnMouseDown(self, button, *args):
    530         if not self._dragging and button == uiconst.MOUSELEFT and self.enabled:
    531             self._dragging = True
    532             self._update_selected_position()
    533 
    534     def OnMouseMove(self, *args):
    535         if self._dragging:
    536             self._update_selected_position()
    537 
    538     def OnMouseUp(self, button, *args):
    539         if self._dragging and button == uiconst.MOUSELEFT:
    540             self._update_selected_position()
    541             self._stop_dragging()
    542 
    543     def UpdateAlignment(self, budgetLeft = 0, budgetTop = 0, budgetWidth = 0, budgetHeight = 0, updateChildrenOnly = False):
    544         result = super(Picker2D, self).UpdateAlignment(budgetLeft, budgetTop, budgetWidth, budgetHeight, updateChildrenOnly)
    545         _, _, _, _, size_changed = result
    546         if size_changed:
    547             self._update_indicator_position()
    548         return result
    549 
    550 
    551 class CircleIndicator(Sprite):
    552 
    553     def __init__(self, width = 15, height = 15, texturePath = 'res:/UI/Texture/classes/ColorPicker/color_selection_indicator.png', state = uiconst.UI_DISABLED, **kwargs):
    554         super(CircleIndicator, self).__init__(width=width, height=height, texturePath=texturePath, state=state, **kwargs)
    555 
    556 
    557 class ColorSaturationBrightnessGradient(Container):
    558     _hue_fill = None
    559 
    560     def __init__(self, hue, ignore_color_blind_mode = False, **kwargs):
    561         self._hue = hue
    562         super(ColorSaturationBrightnessGradient, self).__init__(**kwargs)
    563         GradientSprite(parent=self, align=Align.TOALL, rgbData=[(0.0, (0.0, 0.0, 0.0)), (1.0, (0.0, 0.0, 0.0))], alphaData=[(0.0, 0.0), (1.0, 1.0)], rotation=-math.pi / 2.0)
    564         GradientSprite(parent=self, align=Align.TOALL, rgbData=[(0.0, (1.0, 1.0, 1.0)), (1.0, (1.0, 1.0, 1.0))], alphaData=[(0.0, 1.0), (1.0, 0.0)])
    565         self._hue_fill = Fill(parent=self, align=Align.TOALL, color=self._get_color(), ignoreColorBlindMode=ignore_color_blind_mode)
    566 
    567     @property
    568     def hue(self):
    569         return self._hue
    570 
    571     @hue.setter
    572     def hue(self, value):
    573         self._hue = value
    574         if self._hue_fill:
    575             self._hue_fill.color = self._get_color()
    576 
    577     def _get_color(self):
    578         return carbonui.Color.from_hsb(self._hue, 1.0, 1.0)