diff --git a/enable/tests/trait_defs/test_rgba_color_trait.py b/enable/tests/trait_defs/test_rgba_color_trait.py new file mode 100644 index 000000000..adc4b25c7 --- /dev/null +++ b/enable/tests/trait_defs/test_rgba_color_trait.py @@ -0,0 +1,211 @@ +# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX +# All rights reserved. +# +# This software is provided without warranty under the terms of the BSD +# license included in LICENSE.txt and may be redistributed only under +# the conditions described in the aforementioned license. The license +# is also available online at http://www.enthought.com/licenses/BSD.txt +# +# Thanks for using Enthought open source! + +import unittest + +import numpy as np + +from pyface.color import Color +from traits.api import DefaultValue, HasTraits, TraitError +from traits.testing.optional_dependencies import numpy as np, requires_numpy +from traitsui.api import EditorFactory + +from enable.trait_defs.rgba_color_trait import RGBAColor + + +class ColorClass(HasTraits): + + color = RGBAColor() + + +class TestRGBAColor(unittest.TestCase): + + def test_init(self): + trait = RGBAColor() + self.assertEqual(trait.default_value, (1.0, 1.0, 1.0, 1.0)) + + def test_init_name(self): + trait = RGBAColor("rebeccapurple") + self.assertEqual( + trait.default_value, + (0.4, 0.2, 0.6, 1.0), + ) + + def test_init_hex(self): + trait = RGBAColor("#663399ff") + self.assertEqual( + trait.default_value, + (0.4, 0.2, 0.6, 1.0) + ) + + def test_init_color(self): + trait = RGBAColor(Color(rgba=(0.4, 0.2, 0.6, 1.0))) + self.assertEqual( + trait.default_value, + (0.4, 0.2, 0.6, 1.0) + ) + + def test_init_tuple(self): + trait = RGBAColor((0.4, 0.2, 0.6, 1.0)) + self.assertEqual( + trait.default_value, + (0.4, 0.2, 0.6, 1.0) + ) + + def test_init_list(self): + trait = RGBAColor([0.4, 0.2, 0.6, 1.0]) + self.assertEqual( + trait.default_value, + (0.4, 0.2, 0.6, 1.0) + ) + + def test_init_array(self): + trait = RGBAColor(np.array([0.4, 0.2, 0.6, 1.0])) + self.assertEqual( + trait.default_value, + (0.4, 0.2, 0.6, 1.0) + ) + + def test_init_array_structured_dtype(self): + """ Test if "typical" RGBA structured array value works. """ + arr = np.array( + [(0.4, 0.2, 0.6, 1.0)], + dtype=np.dtype([ + ('red', float), + ('green', float), + ('blue', float), + ('alpha', float), + ]), + ) + trait = RGBAColor(arr[0]) + self.assertEqual( + trait.default_value, + (0.4, 0.2, 0.6, 1.0) + ) + + def test_init_invalid(self): + with self.assertRaises(TraitError): + RGBAColor((0.4, 0.2)) + + def test_validate_color(self): + color = (0.4, 0.2, 0.6, 1.0) + trait = RGBAColor() + validated = trait.validate(None, None, Color(rgba=color)) + self.assertIs( + validated, color + ) + + def test_validate_name(self): + color = (0.4, 0.2, 0.6, 1.0) + trait = RGBAColor() + validated = trait.validate(None, None, "rebeccapurple") + self.assertEqual( + validated, color + ) + + def test_validate_hex(self): + color = (0.4, 0.2, 0.6, 1.0) + trait = RGBAColor() + validated = trait.validate(None, None, "#663399ff") + self.assertEqual( + validated, color + ) + + def test_validate_tuple(self): + color = (0.4, 0.2, 0.6, 0.8) + trait = RGBAColor() + validated = trait.validate(None, None, (0.4, 0.2, 0.6, 0.8)) + self.assertEqual( + validated, color + ) + + def test_validate_list(self): + color = (0.4, 0.2, 0.6, 0.8) + trait = RGBAColor() + validated = trait.validate(None, None, [0.4, 0.2, 0.6, 0.8]) + self.assertEqual( + validated, color + ) + + def test_validate_rgb_list(self): + color = (0.4, 0.2, 0.6, 1.0) + trait = RGBAColor() + validated = trait.validate(None, None, [0.4, 0.2, 0.6]) + self.assertEqual( + validated, color + ) + + def test_validate_bad_string(self): + trait = RGBAColor() + with self.assertRaises(TraitError): + trait.validate(None, None, "not a color") + + def test_validate_bad_object(self): + trait = RGBAColor() + with self.assertRaises(TraitError): + trait.validate(None, None, object()) + + def test_info(self): + trait = RGBAColor() + self.assertIsInstance(trait.info(), str) + + def test_default_trait(self): + color_class = ColorClass() + self.assertEqual(color_class.color, (1.0, 1.0, 1.0, 1.0)) + + def test_set_color(self): + color = (0.4, 0.2, 0.6, 1.0) + color_class = ColorClass(color=Color(rgba=color)) + self.assertIs(color_class.color, color) + + def test_set_name(self): + color = (0.4, 0.2, 0.6, 1.0) + color_class = ColorClass(color="rebeccapurple") + self.assertEqual(color_class.color, color) + + def test_set_hex(self): + color = (0.4, 0.2, 0.6, 1.0) + color_class = ColorClass(color="#663399ff") + self.assertEqual(color_class.color, color) + + def test_set_tuple(self): + color = (0.4, 0.2, 0.6, 1.0) + color_class = ColorClass(color=(0.4, 0.2, 0.6, 1.0)) + self.assertEqual(color_class.color, color) + + def test_set_list(self): + color = (0.4, 0.2, 0.6, 1.0) + color_class = ColorClass(color=[0.4, 0.2, 0.6, 1.0]) + self.assertEqual(color_class.color, color) + + def test_set_array(self): + color = (0.4, 0.2, 0.6, 1.0) + color_class = ColorClass(color=np.array([0.4, 0.2, 0.6, 1.0])) + self.assertEqual(color_class.color, color) + + def test_set_structured_dtype(self): + color = (0.4, 0.2, 0.6, 1.0) + arr = np.array( + [(0.4, 0.2, 0.6, 1.0)], + dtype=np.dtype([ + ('red', float), + ('green', float), + ('blue', float), + ('alpha', float), + ]), + ) + color_class = ColorClass(color=arr[0]) + self.assertEqual(color_class.color, color) + + def test_get_editor(self): + trait = RGBAColor() + editor = trait.get_editor() + + self.assertIsInstance(editor, EditorFactory) diff --git a/enable/trait_defs/rgba_color_trait.py b/enable/trait_defs/rgba_color_trait.py index 935ac92e0..b31ac9595 100644 --- a/enable/trait_defs/rgba_color_trait.py +++ b/enable/trait_defs/rgba_color_trait.py @@ -15,114 +15,50 @@ RR is red, GG is green, and BB is blue. """ -from traits.api import Trait, TraitError, TraitFactory -from traits.etsconfig.api import ETSConfig -from traits.trait_base import SequenceTypes - -from .ui.api import RGBAColorEditor +import numpy as np -if ETSConfig.toolkit == "wx": - from traitsui.wx.color_trait import standard_colors +from pyface.color import Color +from pyface.util.color_parser import ColorParseError, parse_text +from traits.api import TraitType +from traits.trait_base import SequenceTypes - def rgba_color(color): - return ( - color.Red() / 255.0, - color.Green() / 255.0, - color.Blue() / 255.0, - 1.0, - ) -elif ETSConfig.toolkit.startswith("qt"): - from traitsui.qt4.color_trait import standard_colors - def rgba_color(color): - return ( - color.red() / 255.0, - color.green() / 255.0, - color.blue() / 255.0, - 1.0, - ) -else: - from traitsui.null.color_trait import standard_colors +class RGBAColor(TraitType): + """ A Trait which casts Pyface Colors, strings and tuples to RGBA tuples. + """ - def rgba_color(color): + def __init__(self, value="white", **metadata): + default_value = self.validate(None, None, value) + super().__init__(default_value, **metadata) + + def validate(self, object, name, value): + if isinstance(value, Color): + return value.rgba + if isinstance(value, str): + try: + _, value = parse_text(value) + except ColorParseError: + self.error(object, name, value) + is_array = isinstance(value, (np.ndarray, np.void)) + if is_array or isinstance(value, SequenceTypes): + value = tuple(value) + if len(value) == 3: + value += (1.0,) + if len(value) == 4: + return value + + self.error(object, name, value) + + def info(self): return ( - ((color >> 16) & 0xFF) / 255.0, - ((color >> 8) & 0xFF) / 255.0, - (color & 0xFF) / 255.0, - 1.0, + "a Pyface Color, a #-hexadecimal rgb or rgba string, a standard " + "color name, or a sequence of RGBA or RGB values between 0 and 1" ) - -# ----------------------------------------------------------------------------- -# Convert a value into an Enable/Kiva color: -# ----------------------------------------------------------------------------- - -def convert_to_color(object, name, value): - """ Converts a value to an Enable or Kiva color. - """ - if (isinstance(value, SequenceTypes) - and (len(value) == 4) - and (0.0 <= value[0] <= 1.0) - and (0.0 <= value[1] <= 1.0) - and (0.0 <= value[2] <= 1.0) - and (0.0 <= value[3] <= 1.0)): - return value - if isinstance(value, int): - result = ( - ((value >> 24) & 0xFF) / 255.0, - ((value >> 16) & 0xFF) / 255.0, - ((value >> 8) & 0xFF) / 255.0, - (value & 0xFF) / 255.0, - ) - return result - raise TraitError - - -convert_to_color.info = ( - "a tuple of the form (red,green,blue,alpha), where " - "each component is in the range from 0.0 to 1.0, or " - "an integer which in hex is of the form 0xAARRGGBB, " - "where AA is alpha, RR is red, GG is green, and BB is " - "blue" -) - -# ----------------------------------------------------------------------------- -# Standard colors: -# ----------------------------------------------------------------------------- - -# RGBA versions of standard colors -rgba_standard_colors = {} -for name, color in standard_colors.items(): - rgba_standard_colors[name] = rgba_color(color) -rgba_standard_colors["clear"] = (0, 0, 0, 0) - - -# ----------------------------------------------------------------------------- -# Define Enable/Kiva specific color traits: -# ----------------------------------------------------------------------------- - -def RGBAColorFunc(*args, **metadata): - """ Returns a trait whose value must be a GUI toolkit-specific RGBA-based - color. - - Description: - For wxPython, the returned trait accepts any of the following values: - - * A tuple of the form (*r*, *g*, *b*, *a*), in which *r*, *g*, *b*, and *a* - represent red, green, blue, and alpha values, respectively, and are - floats in the range from 0.0 to 1.0 - * An integer whose hexadecimal form is 0x*AARRGGBB*, where *AA* is the - alpha (transparency) value, *RR* is the red value, *GG* is the green - value, and *BB* is the blue value - - Default Value: - For wxPython, (1.0, 1.0, 1.0, 1.0) (that is, opaque white) - """ - tmp_trait = Trait( - "white", convert_to_color, rgba_standard_colors, editor=RGBAColorEditor - ) - return tmp_trait(*args, **metadata) + def create_editor(self): + from .ui.api import RGBAColorEditor + return RGBAColorEditor() -RGBAColorTrait = TraitFactory(RGBAColorFunc) -RGBAColor = RGBAColorTrait +# synonym for backwards compatibility +RGBAColorTrait = RGBAColor