Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions enable/tests/trait_defs/test_rgba_color_trait.py
Original file line number Diff line number Diff line change
@@ -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)
140 changes: 38 additions & 102 deletions enable/trait_defs/rgba_color_trait.py
Original file line number Diff line number Diff line change
Expand Up @@ -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