Repository: MyreMylar/pygame_gui (language: python)

Issue:
Different Theming Color Options and Formats
**Is your feature request related to a problem? Please describe.**
There's a lack of ways to describe a color in a theming file, since only hex values are currently accepted. This isn't necessarily a "problem," but more of an extension that I felt was missing

**Describe the solution you'd like**
Like in CSS, there'd be options to include explicit default color names like "red" or "black"
Additionally, there'd be other formats for different color models to define colors, like rgb, rgba, hsl, and cmy. These values should all be able to work in the same places that hex values currently work, including in gradients

**Describe alternatives you've considered**
I've also considered a way to describe a custom color string definition in a theming file that can be used throughout that file in place of a color value. For example, the theming file could have a prefab for a color named "royalblue", then that "royalblue" color could be referred to throughout the theming file and recognized as its defined color.
However, since there's no current way to define a "prefab" or variable value in a theming file, I'd want to check if there are any other requests for similar features that could be batched under "defaults" or some other location
Lastly, I'd also considered adding a way to use an array value of 3 or 4 values between 0 and 255 in the JSON theming file to also express colors in RGB or RGBA format if the user would prefer to express their colors in that way

**Additional context**
I've begun work on a solution that is able to meet the criteria denoted in the "**Describe the solution you'd like**" section, but I wanted to gague the amount of interest in such a feature and if it conflicts with the direction of pygame_gui as a whole.

Thanks in advance!


Discussion / hints:

Fix patch:
diff --git a/docs/build_docs.bat b/docs/build_docs.bat
old mode 100644
new mode 100755
diff --git a/docs/source/pygame_gui.core.rst b/docs/source/pygame_gui.core.rst
index c3b5502c..def988fe 100644
--- a/docs/source/pygame_gui.core.rst
+++ b/docs/source/pygame_gui.core.rst
@@ -20,6 +20,14 @@ pygame\_gui.core.colour\_gradient module
    :no-undoc-members:
    :show-inheritance:
 
+pygame\_gui.core.colour\_parser module
+----------------------------------------
+
+.. automodule:: pygame_gui.core.colour_parser
+   :members:
+   :no-undoc-members:
+   :show-inheritance:
+
 pygame\_gui.core.surface\_cache module
 --------------------------------------
 
diff --git a/docs/source/theme_guide.rst b/docs/source/theme_guide.rst
index 34a5c262..750d2486 100644
--- a/docs/source/theme_guide.rst
+++ b/docs/source/theme_guide.rst
@@ -1,3 +1,4 @@
+
 .. _theme-guide:
 
 Theme Guide
@@ -55,6 +56,168 @@ within the colours block you can start to set individual colours by their IDs. I
         }
     }
 
+| To add on, you don't only have to use hex values if you prefer, but you also can use the different supported colour syntax. It's all up to preference
+
+.. raw:: html
+
+    <style>
+      .red {color:red}
+      .blue {color:blue}
+      .example-hex { color: #A2F3BB }
+      .example-hex-with-alpha { color: #C2558F9F }
+      .example-shorthand-hex { color: #FAB }
+      .example-shorthand-hex-with-alpha { color: #AF2F }
+      .example-rgb { color: rgb(20, 230, 43) }
+      .example-rgba { color: rgba(60, 30, 97, 1) }
+      .example-hsl { color: hsl(190deg, 40%, 50%) }
+      .example-hsla { color: hsla(10deg, 40%, 50%, 70%) }
+      .example-hsv { color: hsl(282, 79%, 63%) }
+      .example-hsva { color: hsla(20, 25%, 40%, 70%) }
+      .example-cmy { color: #B3804D }
+    </style>
+
+.. role:: red
+.. role:: blue
+.. role:: example-hex
+.. role:: example-hex-with-alpha
+.. role:: example-shorthand-hex
+.. role:: example-shorthand-hex-with-alpha
+.. role:: example-rgb
+.. role:: example-rgba
+.. role:: example-hsl 
+.. role:: example-hsla
+.. role:: example-hsv
+.. role:: example-hsva
+.. role:: example-cmy
+
+
+
+
+.. list-table:: Supported Colour Inputs
+    :widths: 10 10 40 40 
+
+    * - Name
+      - Accepted Value Types
+      - Example
+      - Notes
+    * - Colour Name (**recommended**)
+      - A valid string representation of a colour
+      - :blue:`blue`
+      - A List of valid colour names can be found `here <https://w3schools.sinsixx.com/css/css_colornames.asp.htm>`_
+    * - Hex
+      - 6 Hexidecimal Digits
+      - :example-hex:`#A2F3BB`
+      - Native Pygame Color
+    * - Hex (With Alpha)
+      - 8 Hexidecimal Digits
+      - :example-hex-with-alpha:`#C2558F9F`
+      - Native Pygame Color
+    * - Shorthand Hex
+      - 3 Hexidecimal Digits
+      - :example-shorthand-hex:`#FAB`
+      - Expands doubly (e.g. would be #FFAABB)
+    * - Shorthand Hex (With Alpha)
+      - 4 Hexidecimal Digits
+      - :example-shorthand-hex-with-alpha:`#AF2F`
+      - Expands doubly (e.g. would be #AAFF22FF)
+    * - RGB
+      - 3 numbers between 0 and 255
+      - :example-rgb:`rgb(20, 230, 43)`
+      - Red, Green, Blue
+    * - RGBA
+      - 4 numbers between 0 and 255
+      - :example-rgba:`rgba(60, 30, 97, 255)`
+      - Red, Green, Blue, Alpha
+    * - HSL
+      - Degree, 2 Percentage Values
+      - :example-hsl:`hsl(190deg, 40%, .5)`
+      - Hue, Saturation, Luminence
+    * - HSLA
+      - Degree, 3 Percentage Values
+      - :example-hsla:`hsla(10deg, 40%, .5, 0.7)`
+      - Hue, Saturation, Luminence, Alpha
+    * - HSV
+      - Degree, 2 Percentage Values
+      - :example-hsv:`hsv(282deg, 63%, 92%)`
+      - Hue, Saturation, Value
+    * - HSVA
+      - Degree, 3 Percentage Values
+      - :example-hsva:`hsva(20deg, 40%, .5, 70%)`
+      - Hue, Saturation, Value, Alpha
+    * - CMY
+      - 3 percentage values
+      - :example-cmy:`cmy(.3, .5, .7)`
+      - Cyan, Magneta, Yellow
+
+.. list-table:: Value Types
+    :widths: 10 40 20 20 20
+
+    * - Name
+      - Description   
+      - Examples
+      - Invalid Examples
+      - Used In
+    * - Degree
+      - An Integer Value Bounded from 0 to 360
+            * Can optionally have the "deg" unit appended to the end
+      - 270, 30deg, 45deg 
+      - 37.5, 32.8deg, -15deg, -22
+      - HSL, HSLA, HSV, HSVA
+    * - Percentage
+      - Can accept data in two formats:
+            * A float value bounded from 0 to 1
+            * A percentage value with an integer value bounded from 0 to 100
+      - 40%, 0.67, 27%
+      - 0.4%, 2, 200%
+      - HSL, HSLA, HSV, HSVA, CMY
+    * - U8
+      - An integer value bounded from 0 to 255
+      - 12, 55, 154, 243, 255, 0
+      - 12.4, -23, -27.4, 20.0
+      - RGB, RGBA
+
+
+
+.. note:: If you'd like to easily find a custom colour that is not provided, `colorpicker.me <https://www.colorpicker.me>`_ by `QvCool <https://www.qvcool.com/>`_ is a great tool to choose a colour value to use and paste in your theme file
+
+
+.. code-block:: json
+    :caption: theme.json with different valid colour expressions
+    :linenos:
+
+    {
+        "defaults":
+        {
+            "colours":
+            {
+                "normal_bg": "#f2f",
+                "hovered_bg": "#ff7a",
+                "disabled_bg": "rgb(200, 150, 60)",
+                "selected_bg": "rgba(20, 50, 89, 225)",
+                "dark_bg": "hsl(30, 0.6, 0.7)",
+                "normal_text": "hsla(3deg, 0.5, 0.7, 90%)",
+                "hovered_text": "#FFFFFF",
+                "selected_text": "purple",
+                "disabled_text": "RGB(40, 70, 90)",
+                "link_text": "HSV(50deg, 30%, 40%)",
+                "link_hover": "cmy(50%, 30%, 0.7)",
+                "link_selected": "teal",
+                "text_shadow": "skyblue",
+                "normal_border": "gold",
+            }
+        }
+    }
+
+.. note:: Some More Notes About Colours
+
+    - In shorthand hex values like **#fff** and **#ffff**, each value represents itself twice. for example: **#123** == **#112233** and **#1234** == **#11223344**
+    - Color Model names are **not** case sensitive, you can write *RGB*, *rGb*, or *Rgb* anyway you like, it will not affect the validity of the colour string
+    - Hex values are also **not** case sensitive, **#FFF** and **#fff** are exactly the same
+    - Colour names are **not** case sensitive, :red:`RED`, :red:`Red`, and :red:`red` are all the same colour
+
+    
+
+
 Of course, colours are not just colours - they can also be gradients, which have a very similar syntax. Like so:
 
 .. code-block:: json
@@ -66,7 +229,8 @@ Of course, colours are not just colours - they can also be gradients, which have
         {
             "colours":
             {
-               "normal_bg":"#45494e,#65696e,90"
+               "normal_bg":"#45494e,#65696e,90",
+               "hovered_bg":"rgb(30, 40, 60),#f3f,rgb(50, 60, 70),90deg"
             }
         }
    }
diff --git a/pygame_gui/_constants.py b/pygame_gui/_constants.py
index 542a17fc..0e774de7 100644
--- a/pygame_gui/_constants.py
+++ b/pygame_gui/_constants.py
@@ -115,3 +115,154 @@ def __eq__(self, other):
            'UITextEffectType',
            'OldType'
            ]
+
+_namedColours = {
+  "aliceblue": "#f0f8ff",
+  "antiquewhite": "#faebd7",
+  "aqua": "#00ffff",
+  "aquamarine": "#7fffd4",
+  "azure": "#f0ffff",
+  "beige": "#f5f5dc",
+  "bisque": "#ffe4c4",
+  "black": "#000000",
+  "blanchedalmond": "#ffebcd",
+  "blue": "#0000ff",
+  "blueviolet": "#8a2be2",
+  "brown": "#a52a2a",
+  "burlywood": "#deb887",
+  "cadetblue": "#5f9ea0",
+  "chartreuse": "#7fff00",
+  "chocolate": "#d2691e",
+  "coral": "#ff7f50",
+  "cornflowerblue": "#6495ed",
+  "cornsilk": "#fff8dc",
+  "crimson": "#dc143c",
+  "cyan": "#00ffff",
+  "darkblue": "#00008b",
+  "darkcyan": "#008b8b",
+  "darkgoldenrod": "#b8860b",
+  "darkgray": "#a9a9a9",
+  "darkgreen": "#006400",
+  "darkgrey": "#a9a9a9",
+  "darkkhaki": "#bdb76b",
+  "darkmagenta": "#8b008b",
+  "darkolivegreen": "#556b2f",
+  "darkorange": "#ff8c00",
+  "darkorchid": "#9932cc",
+  "darkred": "#8b0000",
+  "darksalmon": "#e9967a",
+  "darkseagreen": "#8fbc8f",
+  "darkslateblue": "#483d8b",
+  "darkslategray": "#2f4f4f",
+  "darkslategrey": "#2f4f4f",
+  "darkturquoise": "#00ced1",
+  "darkviolet": "#9400d3",
+  "deeppink": "#ff1493",
+  "deepskyblue": "#00bfff",
+  "dimgray": "#696969",
+  "dimgrey": "#696969",
+  "dodgerblue": "#1e90ff",
+  "firebrick": "#b22222",
+  "floralwhite": "#fffaf0",
+  "forestgreen": "#228b22",
+  "fuchsia": "#ff00ff",
+  "gainsboro": "#dcdcdc",
+  "ghostwhite": "#f8f8ff",
+  "goldenrod": "#daa520",
+  "gold": "#ffd700",
+  "gray": "#808080",
+  "green": "#008000",
+  "greenyellow": "#adff2f",
+  "grey": "#808080",
+  "honeydew": "#f0fff0",
+  "hotpink": "#ff69b4",
+  "indianred": "#cd5c5c",
+  "indigo": "#4b0082",
+  "ivory": "#fffff0",
+  "khaki": "#f0e68c",
+  "lavenderblush": "#fff0f5",
+  "lavender": "#e6e6fa",
+  "lawngreen": "#7cfc00",
+  "lemonchiffon": "#fffacd",
+  "lightblue": "#add8e6",
+  "lightcoral": "#f08080",
+  "lightcyan": "#e0ffff",
+  "lightgoldenrodyellow": "#fafad2",
+  "lightgray": "#d3d3d3",
+  "lightgreen": "#90ee90",
+  "lightgrey": "#d3d3d3",
+  "lightpink": "#ffb6c1",
+  "lightsalmon": "#ffa07a",
+  "lightseagreen": "#20b2aa",
+  "lightskyblue": "#87cefa",
+  "lightslategray": "#778899",
+  "lightslategrey": "#778899",
+  "lightsteelblue": "#b0c4de",
+  "lightyellow": "#ffffe0",
+  "lime": "#00ff00",
+  "limegreen": "#32cd32",
+  "linen": "#faf0e6",
+  "magenta": "#ff00ff",
+  "maroon": "#800000",
+  "mediumaquamarine": "#66cdaa",
+  "mediumblue": "#0000cd",
+  "mediumorchid": "#ba55d3",
+  "mediumpurple": "#9370db",
+  "mediumseagreen": "#3cb371",
+  "mediumslateblue": "#7b68ee",
+  "mediumspringgreen": "#00fa9a",
+  "mediumturquoise": "#48d1cc",
+  "mediumvioletred": "#c71585",
+  "midnightblue": "#191970",
+  "mintcream": "#f5fffa",
+  "mistyrose": "#ffe4e1",
+  "moccasin": "#ffe4b5",
+  "navajowhite": "#ffdead",
+  "navy": "#000080",
+  "oldlace": "#fdf5e6",
+  "olive": "#808000",
+  "olivedrab": "#6b8e23",
+  "orange": "#ffa500",
+  "orangered": "#ff4500",
+  "orchid": "#da70d6",
+  "palegoldenrod": "#eee8aa",
+  "palegreen": "#98fb98",
+  "paleturquoise": "#afeeee",
+  "palevioletred": "#db7093",
+  "papayawhip": "#ffefd5",
+  "peachpuff": "#ffdab9",
+  "peru": "#cd853f",
+  "pink": "#ffc0cb",
+  "plum": "#dda0dd",
+  "powderblue": "#b0e0e6",
+  "purple": "#800080",
+  "rebeccapurple": "#663399",
+  "red": "#ff0000",
+  "rosybrown": "#bc8f8f",
+  "royalblue": "#4169e1",
+  "saddlebrown": "#8b4513",
+  "salmon": "#fa8072",
+  "sandybrown": "#f4a460",
+  "seagreen": "#2e8b57",
+  "seashell": "#fff5ee",
+  "sienna": "#a0522d",
+  "silver": "#c0c0c0",
+  "skyblue": "#87ceeb",
+  "slateblue": "#6a5acd",
+  "slategray": "#708090",
+  "slategrey": "#708090",
+  "snow": "#fffafa",
+  "springgreen": "#00ff7f",
+  "steelblue": "#4682b4",
+  "tan": "#d2b48c",
+  "teal": "#008080",
+  "thistle": "#d8bfd8",
+  "tomato": "#ff6347",
+  "turquoise": "#40e0d0",
+  "violet": "#ee82ee",
+  "wheat": "#f5deb3",
+  "white": "#ffffff",
+  "whitesmoke": "#f5f5f5",
+  "yellow": "#ffff00",
+  "yellowgreen": "#9acd32"
+}
\ No newline at end of file
diff --git a/pygame_gui/core/colour_parser.py b/pygame_gui/core/colour_parser.py
new file mode 100644
index 00000000..e281d90b
--- /dev/null
+++ b/pygame_gui/core/colour_parser.py
@@ -0,0 +1,710 @@
+"""
+pygame_gui.core.colour_parser.py
+A Functional Based Module for the parsing of Colour Strings in pygame_gui
+
+Use Notes:
+    Mostly all that one will ever need is these 5 functions 
+        - **parse_colour_or_gradient_string**: Attempt to parse a string that may be a colour or gradient into its respective value
+        - **is_valid_colour_string**: Check if a string represents a valid pygame Color
+        - **is_valid_gradient_string**: Check if a string represents a valid ColourGradient
+        - **parse_colour_string**: Parse a string into a pygame Color
+        - **parse_gradient_string**: Parse a string into a ColourGradient
+        
+    The documentation for what counts as a 'Valid' colour and gradient string can be found in the Theme Guide of the pygame_gui documentation at https://pygame-gui.readthedocs.io/en/v_065/theme_guide.html
+
+Developer Notes:
+    How it Works:
+        This module works through pairs of validating and parsing functions at the value, colour, and gradient levels
+        Hopefully everything should be generic enough that any new colour models or representations can be easily added without much bloat or *magic*
+
+        Parsing A Colour:
+            Simply, The parsing of a colour is done by simply checking if there is any valid function cashed in the _colourParsers dictionary, then calling the paired parsing function to get its value
+            Therefore, this system is not conrete at all, and should be extremely extensible to add **any 2 functions that can validate and parse a developer-determined schema** 
+
+        Parsing A Gradient:
+            As of now, the gradient parser is implemented in such a way where it assumes that all commas outside of an enclosing glyph ( Any comma not inside of a (), [], or {} ) is a separator in a gradient list
+            Generally, if creating new colour string schemas, this will break if there is a new colour which uses commas not enclosed in a glyph. This shouldn't be a problem right now, but it is worth noting as a warning in case of any additions to this parser
+            TL,DR: Dev life will be easier if it is ensured that commas in colour schemas are inside of parentheses, brackets, or curly braces ( like "rgb(20, 20, 20)" )
+"""
+
+import pygame
+from typing import Callable, Union, Iterable, TypeVar, TypedDict, Optional, Type
+import enum
+import warnings
+from pygame_gui.core.colour_gradient import ColourGradient
+from pygame_gui.core.utility import premul_col
+from pygame_gui._constants import _namedColours
+
+
+class NumParserType(enum.Enum):
+    """Enum for the supported value types in colour strings"""
+    INT = "INT"
+    FLOAT = "FLOAT"
+    PERCENTAGE = "PERCENTAGE"
+    DEGREE = "DEGREE"
+    U8 = "U8"
+
+T = TypeVar("T")
+
+def is_num_str(string: str) -> bool:
+    try:
+        float(string)
+        return True
+    except ValueError:
+        return False
+
+def is_int_str(string: str) -> bool:
+    return is_num_str(string) and not "." in string
+
+def is_float_str(string: str):
+    return is_num_str(string) and "." in string
+
+def is_degree_string(strdata: str) -> bool:
+    if len(strdata) > 0:
+        if strdata.endswith("deg") and len(strdata) > 3:
+            if is_int_str(strdata[:-3]):
+                degrees = int(strdata[:-3]) 
+                return degrees >= 0 and degrees <= 360
+        elif is_int_str(strdata):
+            degrees = int(strdata)
+            return degrees >= 0 and degrees <= 360
+    return False
+
+def parse_degree_string(strdata: str) -> int:
+    if len(strdata) > 0:
+        if strdata.endswith("deg"):
+            return int(strdata[:-3])
+        return int(strdata)
+
+
+def is_percentage_string(strdata: str) -> bool:
+    if len(strdata) > 0:
+        if strdata[-1] == "%" and is_int_str(strdata[:-1]):
+            return True
+        
+        if is_float_str(strdata):
+            floatvalue = float(strdata)
+            return floatvalue >= 0 and floatvalue <= 1
+    return False
+
+def parse_percentage_string(strdata: str) -> float:
+    if is_float_str(strdata):
+        return float(strdata)
+    return float(strdata[:-1]) / 100
+
+def is_u8_string(strdata: str) -> bool:
+    return is_int_str(strdata) and int(strdata) >= 0 and int(strdata) <= 255
+
+def parse_u8_string(strdata: str) -> int:
+    return int(strdata)
+
+ColourValueValidator = Callable[[str], bool]
+ColourValueParser = Callable[[str], T]
+class ColourValueParserData(TypedDict):
+    validator: ColourValueValidator
+    parser: ColourValueParser
+
+_valueParsers: dict[NumParserType, ColourValueParserData] = {
+    NumParserType.PERCENTAGE: { "validator": is_percentage_string, "parser": parse_percentage_string },
+    NumParserType.U8: { "validator": is_u8_string, "parser": parse_u8_string },
+    NumParserType.DEGREE: { "validator": is_degree_string, "parser": parse_degree_string },
+    NumParserType.FLOAT: { "validator": is_float_str, "parser": lambda string: float(string) },
+    NumParserType.INT: { "validator": is_int_str, "parser": lambda string: int(string) }
+} 
+""" A mapping for each NumParserType to its corresponding validator and parser for strings of that type's specification  """
+
+
+_colourModelSchemas: dict[str, list[NumParserType]] = {
+    "hsl": [NumParserType.DEGREE, NumParserType.PERCENTAGE, NumParserType.PERCENTAGE],
+    "hsla": [NumParserType.DEGREE, NumParserType.PERCENTAGE, NumParserType.PERCENTAGE, NumParserType.PERCENTAGE],
+    "hsv": [NumParserType.DEGREE, NumParserType.PERCENTAGE, NumParserType.PERCENTAGE],
+    "hsva": [NumParserType.DEGREE, NumParserType.PERCENTAGE, NumParserType.PERCENTAGE, NumParserType.PERCENTAGE],
+    "rgb": [NumParserType.U8, NumParserType.U8, NumParserType.U8],
+    "rgba": [NumParserType.U8, NumParserType.U8, NumParserType.U8, NumParserType.U8],
+    "cmy": [NumParserType.PERCENTAGE, NumParserType.PERCENTAGE, NumParserType.PERCENTAGE],
+}
+"""Mapping for each supported colour model to the values that they take in"""
+
+def validate_colour_model(strdata: str, name: str, types: list[NumParserType]) -> bool:
+    """Use a colour model's name and types (generally denoted by its name in the _colourModelSchemas dictionary) to validate its use
+        Developer Notes:
+            - This function depends on the validator of each value type being present in the _valueParsers dictionary
+            - This function assumes that the colour model also takes up the form "name(value, value...)"
+    
+    :param strdata: the colour model string to validate
+    :type strdata: str
+    :param name: the name of the colour model
+    :type name: str
+    :param types: the types of the values in the colour model in order, used to validate each value individually
+    :type types: list[NumParserType]
+    :return: A boolean on whether the colour model could find a working validator
+    :rtype: bool
+    """
+
+    if strdata.lower().startswith(f'{name}(') and strdata.endswith(")"):
+        componentStrings = [ component.strip() for component in strdata[(len(name) + 1):-1].split(",") ]
+        if len(types) == len(componentStrings):
+            if all([ valueParserType in _valueParsers for valueParserType in types ]):
+                return all([ _valueParsers[types[i]]["validator"](componentStrings[i]) for i in range(len(componentStrings)) ])
+    return False
+
+def parse_colour_model(strdata: str, name: str, types: list[NumParserType]) -> tuple:
+    """Use a colour model's name and types (generally denoted by its name in the _colourModelSchemas dictionary) to return a tuple of its containing values
+        Developer Notes:
+            - This function depends on the parser of each value type being present in the _valueParsers dictionary
+            - This function assumes that the strdata parameter has **already been validated** against the validate_colour_model function
+            - This function assumes that the colour model also takes up the form "name(value, value...)"
+    
+    :param strdata: the colour model string to parse
+    :type strdata: str
+    :param name: the name of the colour model
+    :type name: str
+    :param types: the types of the values in the colour model in order, used to parse each value individually
+    :type types: list[NumParserType]
+    :return: A tuple containing all of the parsed values in the colour model
+    :rtype: tuple
+    :raises ValueError: if the colour model does not meet the standard <name(value, ...)> schema
+    :raises KeyError: if the colour model cannot find the parser for a given type or the given type is not defined to have a parser
+    """
+
+    if strdata.lower().startswith(f'{name}(') and strdata.endswith(")"):
+        componentStrings = [ component.strip() for component in strdata[(len(name) + 1):-1].split(",") ]
+        return [ _valueParsers[types[i]]["parser"](componentStrings[i]) for i in range(len(componentStrings)) ]
+    raise ValueError(fr'Given string data {strdata} does not meet the generic colour model schema <name(value, value, ...)>, always validate the colour model string first before parsing')
+
+def is_valid_hex_string(strdata: str) -> bool:
+    """Validate Hex Color string in the format "#FFF", "#FFFF", "#FFFFFF", or "#FFFFFFFF" 
+        Value Parameter Descriptions:
+            F: any hexadecimal digit between 0 and F inclusive
+        Examples:
+            - "#A3F" (Shorthand)
+            - "#98C4" (Shorthand with Alpha)
+            - "#F3FAFF" (Full)
+            - "#BD24A017" (Full with Alpha)
+        
+    :param strdata: the hex string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined hex schema
+    :rtype: bool
+    """
+
+    hexdigits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]
+    validLengths = [4, 5, 7, 9]
+    if strdata.startswith("#"):
+        if len(strdata) in validLengths:
+            return all([ ch.lower() in hexdigits for ch in strdata[1:] ])
+    return False
+
+def expand_shorthand_hex(strdata: str) -> str:
+    """Expand a Shorthand Hex Color
+        Example:
+            #FA2 -> #FFAA22
+
+    :param strdata: the hex string to expand
+    :type strdata: str
+    :returns: An expanded hex string
+    :rtype: str
+    """
+
+    return ("#" + "".join([ ch * 2 for ch in strdata[1:] ]) ).lower()
+
+def parse_hex_string(strdata: str) -> pygame.Color:
+    """Parse Hex Color string in the formats "#FFF", "#FFFF", "#FFFFFF", or "#FFFFFFFF" 
+        Value Parameter Descriptions:
+            - **F**: any hexadecimal digit between 0 and F inclusive
+        Examples:
+            - "#A3F" (Shorthand)
+            - "#98C4" (Shorthand with Alpha)
+            - "#F3FAFF" (Full)
+            - "#BD24A017" (Full with Alpha)
+
+    :param strdata: the hex string to parse
+    :type strdata: str
+    :returns: A pygame Color from the hex data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    if len(strdata) in [4, 5]:
+        return pygame.Color(expand_shorthand_hex(strdata))
+    return pygame.Color(strdata.lower())
+
+def is_valid_rgb_string(strdata: str):
+    """Validate RGB Color string in the format "rgb(uint8, uint8, uint8)"
+        Value Parameter Descriptions:
+            - **uint8**: is an integer value bounded between 0 and 255
+        Examples:
+            - "rgb(20, 40, 80)"
+
+    :param strdata: the rgb string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined rgb schema
+    :rtype: bool
+    """
+
+    return validate_colour_model(strdata, "rgb", _colourModelSchemas["rgb"])
+    
+def parse_rgb_string(strdata: str) -> pygame.Color:
+    """Parse RGB Color string in the format "rgb(uint8, uint8, uint8)" 
+        Value Parameter Descriptions:
+            - **uint8**: is an integer value bounded between 0 and 255
+        Examples:
+            - "rgb(20, 40, 80)"
+
+    :param strdata: the rgb string to parse
+    :type strdata: str
+    :returns: A pygame Color from the data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    return pygame.Color(*parse_colour_model(strdata, "rgb", _colourModelSchemas["rgb"]))
+
+def is_valid_rgba_string(strdata: str) -> bool:
+    """Validate RGBA Color string in the format "rgba(uint8, uint8, uint8, uint8)" 
+        Value Parameter Descriptions:
+            - **uint8**: is an integer value bounded between 0 and 255
+        Examples:
+            - "rgba(30, 241, 174, 232)"
+
+    :param strdata: the rgba string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined rgba schema
+    :rtype: bool
+    """
+
+    return validate_colour_model(strdata, "rgba", _colourModelSchemas["rgba"])
+
+def parse_rgba_string(strdata: str) -> pygame.Color:
+    """Parse RGBA Color string in the format "rgba(uint8, uint8, uint8, uint8)"
+        Value Parameter Descriptions:
+            - **uint8**: is an integer value bounded between 0 and 255
+        Examples:
+            - "rgba(30, 241, 174, 232)"
+
+    :param strdata: the rgba string to parse
+    :type strdata: str
+    :returns: A pygame Color from the rgba data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    return pygame.Color(*parse_colour_model(strdata, "rgba", _colourModelSchemas["rgba"]))
+    
+def is_valid_cmy_string(strdata: str) -> bool:
+    """Validate CMY Color string in the format "cmy(percentage, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+        Examples:
+            - "cmy(.4, .7, 80%)"
+
+    :param strdata: the cmy string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined cmy schema
+    :rtype: bool
+    """
+
+    return validate_colour_model(strdata, "cmy", _colourModelSchemas["cmy"])
+
+def parse_cmy_string(strdata: str) -> pygame.Color:
+    """Parse CMY Color string in the format "cmy(percentage, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+        Examples:
+            - "cmy(.4, .7, 80%)"
+
+    :param strdata: the cmy string to parse
+    :type strdata: str
+    :returns: A pygame Color from the cmy data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    colour = pygame.Color(0, 0, 0)
+    colour.cmy = parse_colour_model(strdata, "cmy", _colourModelSchemas["cmy"]) 
+    return colour
+
+def is_valid_hsl_string(strdata: str) -> bool:
+    """Validate HSL Color string in the format "hsl(degree, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsl(30, 0.6, 0.7)"
+            - "hsl(30deg, 40%, .5)"
+
+    :param strdata: the hsl string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined hsl schema
+    :rtype: bool
+    """
+    return validate_colour_model(strdata, "hsl", _colourModelSchemas["hsl"])
+
+def parse_hsl_string(strdata: str) -> pygame.Color:
+    """Parse HSL Color string in the format "hsl(degree, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsl(30, 0.6, 0.7)"
+            - "hsl(30deg, 40%, .5)"
+
+
+    :param strdata: the hsl string to parse
+    :type strdata: str
+    :returns: A pygame Color from the hsl data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    colour = pygame.Color(0, 0, 0)
+    hsl = parse_colour_model(strdata, "hsl", _colourModelSchemas["hsl"]) 
+    colour.hsla = (hsl[0], int(hsl[1] * 100), int(hsl[2] * 100), 100)
+    return colour
+
+def is_valid_hsla_string(strdata: str) -> bool:
+    """Validate HSLA Color string in the format "hsla(degree, percentage, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsla(30, 0.6, 0.7, 80%)"
+            - "hsla(30deg, 40%, .5, 40%)"
+
+    :param strdata: the hsla string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined hsla schema
+    :rtype: bool
+    """
+
+    return validate_colour_model(strdata, "hsla", _colourModelSchemas["hsla"])
+
+def parse_hsla_string(strdata: str) -> pygame.Color:
+    """Parse HSLA Color string in the format "hsla(degree, percentage, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsla(30, 0.6, 0.7, 80%)"
+            - "hsla(30deg, 40%, .5, 40%)"
+
+
+    :param strdata: the hsla string to parse
+    :type strdata: str
+    :returns: A pygame Color from the hsla data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    colour = pygame.Color(0, 0, 0)
+    hsla = parse_colour_model(strdata, "hsla", _colourModelSchemas["hsla"]) 
+    colour.hsla = (hsla[0], int(hsla[1] * 100), int(hsla[2] * 100), int(hsla[3] * 100))
+    return colour
+
+def is_valid_hsv_string(strdata: str) -> bool:
+    """Validate HSLA Color string in the format "hsv(degree, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsv(50, 30%, .4)"
+            - "hsv(60deg, 40%, 12%)"
+
+    :param strdata: the hsv string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined hsv schema
+    :rtype: bool
+    """
+
+    return validate_colour_model(strdata, "hsv", _colourModelSchemas["hsv"])
+
+def parse_hsv_string(strdata: str) -> pygame.Color:
+    """Parse HSV Color string in the format "hsv(degree, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsv(50, 30%, .4)"
+            - "hsv(60deg, 40%, 12%)"
+
+
+    :param strdata: the hsv string to parse
+    :type strdata: str
+    :returns: A pygame Color from the hsv data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    colour = pygame.Color(0, 0, 0)
+    hsv = parse_colour_model(strdata, "hsv", _colourModelSchemas["hsv"]) 
+    colour.hsva = (hsv[0], int(hsv[1] * 100), int(hsv[2] * 100), 100)
+    return colour
+
+def parse_hsva_string(strdata: str) -> pygame.Color:
+    """Parse HSVA Color string in the format "hsva(degree, percentage, percentage, percentage)"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsva(50, .3, 40%, .75)"
+            - "hsva(60deg, 40%, 12%, .94)"
+
+
+    :param strdata: the hsva string to parse
+    :type strdata: str
+    :returns: A pygame Color from the hsva data parsed from strdata
+    :rtype: pygame.Color
+    """
+
+    colour = pygame.Color(0, 0, 0)
+    hsva = parse_colour_model(strdata, "hsva", _colourModelSchemas["hsva"]) 
+    colour.hsva = (hsva[0], int(hsva[1] * 100), int(hsva[2] * 100), int(hsva[3] * 100))
+    return colour
+
+def is_valid_hsva_string(strdata: str) -> bool:
+    """Validate HSVA Color string in the format "*hsva(degree, percentage, percentage, percentage)*"
+        Value Parameter Descriptions:
+            - **percentage**: either an integer value from 0 to 100 with "%" appended at the end or a float value ranging from 0 to 1
+            - **degree**: a value between 0 to 360 with the "deg" unit optionally appended to the end
+        Examples:
+            - "hsva(50, .3, 40%, .75)"
+            - "hsva(60deg, 40%, 12%, .94)"
+
+
+    :param strdata: the hsva string to validate
+    :type strdata: str
+    :returns: A boolean determining whether the string fits the determined hsva schema or not
+    :rtype: bool
+    """
+
+    return validate_colour_model(strdata, "hsva", _colourModelSchemas["hsva"])
+
+def is_valid_colour_name(strdata: str):
+    """| Validate Colour name string to be recognizable by pygame_gui as a valid colour_name
+    | As of the writing of these documentations, all colour names defined are from the CSS colours given by all major browsers, which can be found here: https://w3schools.sinsixx.com/css/css_colornames.asp.htm
+    | All colour names are not case-sensitive, so RED, Red, and red all represent the same value
+    
+    :param strdata: the colour name to validate
+    :type strdata: str
+    :returns: A boolean determining whether strdata is recognized as a colour name and has a locatable colour value
+    :rtype: bool
+    """
+
+    return strdata.lower() in _namedColours
+
+def parse_colour_name(strdata: str) -> pygame.Color:
+    """| Parse Colour name string into its corresponding colour value
+    | As of the writing of these documentations, all colour names defined are from the CSS colours given by all major browsers, which can be found here: https://w3schools.sinsixx.com/css/css_colornames.asp.htm
+    | All colour names are not case-sensitive, so RED, Red, and red all represent the same value
+
+    :param strdata: The colour name to parse
+    :type strdata: str
+    :returns: A pygame Color from the colour name given by strdata
+    :rtype: pygame.Color
+    :raises KeyError: If the colour corresponding to the strdata passed in could not be found ( always call is_valid_colour_name first to check )
+    """
+
+    return pygame.Color(_namedColours[strdata.lower()])
+
+ColourStringValidator = Callable[[str], bool]
+ColourStringParser = Callable[[str], pygame.Color]
+_colourParsers: list[tuple[ColourStringValidator, ColourStringParser]] = [
+    (is_valid_colour_name, parse_colour_name),
+    (is_valid_hex_string, parse_hex_string),
+    (is_valid_rgb_string, parse_rgb_string),
+    (is_valid_hsl_string, parse_hsl_string),
+    (is_valid_cmy_string, parse_cmy_string),
+    (is_valid_rgba_string, parse_rgba_string),
+    (is_valid_hsla_string, parse_hsla_string),
+    (is_valid_hsv_string, parse_hsv_string),
+    (is_valid_hsva_string, parse_hsva_string) ]
+"""The list of validator and parser function pairs that will be used by is_valid_colour_string and parse_colour_string
+The functions have no requirement other than the validator confirming a schema and the parser returning a pygame.Color value from the string data if it is valid
+Note that if new validator and parsing formulas are added, these validators should not overlap if it can be helped, as there's no way to determine whether one colour could be more "valid" than another in that case
+"""
+
+def is_valid_colour_string(strdata: str) -> bool:
+    """Validate a colour string using the available colour string validator and parsers in pygame_gui
+    
+    :param strdata: the colour string data to validate
+    :return: A boolean on whether any valid parser could be found for the string data
+    :rtype: bool
+    """
+
+    return any([ validator(strdata) for validator, _ in _colourParsers])
+
+def parse_colour_string(strdata: str) -> Optional[pygame.Color]:
+    """ Parse a Color String
+        Developer Notes:
+            - This function uses the implemented colour parsing and colour validating functions available through _colourParsers in order to determine a proper colour data
+            - Additionally, named colour strings are taken into account firstly when determining the data of the colour
+            - Note that this function returns the first valid occurance that they find
+    
+    :param strdata: The string to parse into a Colour
+    :type strdata: str 
+    :return: A Pygame Color Object from the colour data parsed from strdata or None if the strdata is an invalid colour string
+    :rtype: pygame.Color or None
+    """
+
+    for validator, parser in _colourParsers:
+        if validator(strdata):
+            return parser(strdata)
+    return None
+
+def valid_enclosing_glyphs(strdata: str) -> tuple[bool, list[str]]:
+    """Find if each opening parenthesis in a string has a valid closing parenthesis and vice versa
+        Developer Notes:
+            - Used to determine which top level commas should be used to separate gradients
+            - Used with the assumption that colour values themselves do not have glyphs that are left opened
+    
+    :param strdata: the string to check
+    :type strdata: str
+    :returns: A boolean determining whether each opening parenthesis has a closing parenthesis and each closing parenthesis has an open parenthesis
+    :rtype: bool
+    """
+
+    glyphs: dict[str, str] = {
+        ")": "(",
+        "]": "[",
+        "}": "{"
+    }
+
+    openingStack: list[str] = []
+    for ch in strdata:
+        if ch in glyphs.values():
+            openingStack.append(ch)
+        elif ch in glyphs:
+            if len(openingStack) > 0:
+                if openingStack[-1] == glyphs[ch]:
+                    openingStack.pop()
+                    continue
+            return False
+    return len(openingStack) == 0
+
+def get_commas_outside_enclosing_glyphs(strdata: str) -> list[int]:
+    """In the colour_parser module, This function is used to determine where to split gradient strings in order to get the full list of colours and the final degrees
+        Developer Notes: 
+            - Used to determine which top level commas should be used to separate gradients
+            - Used with the assumption that colour values themselves do not have glyphs that are left opened
+            - Used with the assumption that top level commas used to separate gradients are not within any sort of enclosing glyph
+            - An **enclosing glyph** is like a parentheses, bracket, curly brace, or something of the sort
+
+    :param strdata: the string to check
+    :type strdata: str
+    :returns: A list of the indices of the commas determined to be outside of any enclosing parentheses
+    :rtype: list[int]
+    """
+
+    glyphs: dict[str, str] = {
+        ")": "(",
+        "]": "[",
+        "}": "{"
+    }
+
+    openingStack = []
+    commaIndicesOutsideParentheses: list[int] = []
+
+    for i in range(len(strdata)):
+        ch = strdata[i]
+        if ch == "," and len(openingStack) == 0:
+            commaIndicesOutsideParentheses.append(i)
+        if ch in glyphs.values():
+            openingStack.append(ch)
+        elif ch in glyphs:
+            if len(openingStack) > 0:
+                if openingStack[-1] == glyphs[ch]:
+                    openingStack.pop()
+                    continue
+            return commaIndicesOutsideParentheses
+    return commaIndicesOutsideParentheses
+
+def may_be_gradient_string(strdata: str) -> bool:
+    """Determine if a string should even be considered for validation as a gradient
+        Developer Notes:
+            - Determines this by determining if there are 2 or 3 commas outside of any enclosing glyph and if the enclosing glyphs in the colour string are validly closed and opened.
+
+    :param strdata: the possible gradient string to check
+    :type strdata: str
+
+    :returns: If the gradient string passes the starting check 
+    :rtype: bool
+    """
+
+    return "," in strdata and valid_enclosing_glyphs(strdata) and len(get_commas_outside_enclosing_glyphs(strdata)) in [2, 3]
+
+
+def split_string_at_indices(strdata: str, indices: Iterable[int]) -> list[str]:
+    """Works similarly to the built-in string split function, where the split data is discarded from the resultant array
+        Developer Notes:
+            - Used in colour_parser module to split gradient strings into their respective colour and angle components
+
+    :param strdata: the string to split
+    :type strdata: str
+    :param indices: the indices to split the string at
+    :type indices: Iterable[int]
+    :return: A list of the split strings after cutting at the specified indices given by the 'indices' parameter
+    :rtype: list[str]
+    """
+
+    splits = []
+    last = 0
+    for i in range(len(indices)):
+        splits.append(strdata[last: indices[i]])
+        last = indices[i] + 1
+        if last >= len(strdata):
+            return splits
+    splits.append(strdata[last:])
+    return splits
+
+def is_valid_gradient_string(strdata: str) -> bool:
+    """Validate a gradient string
+        A gradient string should consist of a 3 or 4 length comma separated list, with the first values being any valid colour strings and the last value representing a degree angle for the direction of the gradient
+        Examples:
+            - "red,blue,40deg"
+            - "#f23,rgb(30, 70, 230),hsv(50, 70%, 90%),50"
+
+    :param strdata: the gradient string to validate
+    :type strdata: str
+    :returns: A boolean indicating whether the gradient string is valid or not
+    :rtype: bool
+    """
+
+    if may_be_gradient_string(strdata):
+        gradientData = [ dataComponent.strip() for dataComponent in split_string_at_indices(strdata, get_commas_outside_enclosing_glyphs(strdata)) ]
+        if is_degree_string(gradientData[-1]):
+            colourStrings = gradientData[:-1]
+            return all([ is_valid_colour_string(colour) for colour in colourStrings ])
+    return False
+
+def parse_gradient_string(strdata: str) -> Optional[ColourGradient]:
+    """Parse a gradient string
+        A gradient string should consist of a 3 or 4 length comma separated list, with the first values being any valid colour strings and the last value representing a degree angle for the direction of the gradient
+        Examples:
+            - "red,blue,40deg"
+            - "#f23,rgb(30, 70, 230),hsv(50, 70%, 90%),50"
+
+    :param strdata: the gradient string to validate
+    :type strdata: str
+    :returns: A ColourGradient object if strdata is a valid gradient string, otherwise None
+    :rtype: bool or None
+    """
+
+    if is_valid_gradient_string(strdata):
+        gradientData = [ dataComponent.strip() for dataComponent in split_string_at_indices(strdata, get_commas_outside_enclosing_glyphs(strdata)) ]
+        gradient_direction = parse_degree_string(gradientData[-1])
+        colours = [ premul_col(parse_colour_string(colour)) for colour in gradientData[:-1] ]
+        return ColourGradient(gradient_direction, *colours)
+    return None
+
+
+def parse_colour_or_gradient_string(strdata: str) -> Optional[Union[pygame.Color, ColourGradient]]:
+    """| Parse a colour or gradient string into a pygame Color or a ColourGradient Object
+    |
+    | The documentation for what counts as a 'Valid' colour and gradient string, including the supported colour formats by pygame_gui, can be found in the Theme Guide of the pygame_gui documentation at https://pygame-gui.readthedocs.io/en/v_065/theme_guide.html
+
+    :param strdata: the string data to parse into a colour or gradient
+    :type strdata: str
+    :param supressWarnings: If the parser should throw warnings in case anything fails
+    :type supressWarnings: bool
+    :return: The colour or gradient generated from the string data, or none
+    :rtype: pygame.Color, ColourGradient, or None
+    """
+
+    if is_valid_gradient_string(strdata):
+        return parse_gradient_string(strdata)
+    elif is_valid_colour_string(strdata):
+        return parse_colour_string(strdata)
+    return None
\ No newline at end of file
diff --git a/pygame_gui/core/ui_appearance_theme.py b/pygame_gui/core/ui_appearance_theme.py
index 9da8c8ed..c786c38d 100644
--- a/pygame_gui/core/ui_appearance_theme.py
+++ b/pygame_gui/core/ui_appearance_theme.py
@@ -4,20 +4,24 @@
 import warnings
 
 from contextlib import contextmanager
-from typing import Union, List, Dict, Any
+from typing import Union, List, Dict, Any, Iterable, Optional, Callable
 
 import pygame
 
 from pygame_gui.core.interfaces.font_dictionary_interface import IUIFontDictionaryInterface
 from pygame_gui.core.interfaces.colour_gradient_interface import IColourGradientInterface
 from pygame_gui.core.interfaces.appearance_theme_interface import IUIAppearanceThemeInterface
-from pygame_gui.core.utility import create_resource_path, PackageResource, premul_col
+from pygame_gui.core.utility import create_resource_path, PackageResource
 from pygame_gui.core.utility import ImageResource, SurfaceResource
 from pygame_gui.core.ui_font_dictionary import UIFontDictionary
 from pygame_gui.core.ui_shadow import ShadowGenerator
 from pygame_gui.core.surface_cache import SurfaceCache
 from pygame_gui.core.colour_gradient import ColourGradient
 from pygame_gui.core.resource_loaders import IResourceLoader
+from pygame_gui._constants import _namedColours
+from pygame_gui.core.colour_parser import parse_colour_or_gradient_string, get_commas_outside_enclosing_glyphs
+
+import enum
 
 # First try importlib
 # Then importlib_resources
@@ -30,6 +34,8 @@
         raise ImportError from no_import_lib
 
 
+
+
 class UIAppearanceTheme(IUIAppearanceThemeInterface):
     """
     The Appearance Theme class handles all the data that styles and generally dictates the
@@ -862,6 +868,7 @@ def _load_element_colour_data_from_theme(self,
         :param element_name: The theming element ID that this data belongs to.
         :param element_theming: The data dictionary from the theming file to load data from.
         """
+
         if element_name not in self.ui_element_colours:
             self.ui_element_colours[element_name] = {}
         colours_dict = element_theming[data_type]
@@ -953,6 +960,7 @@ def _load_colour_defaults_from_theme(self, theme_dict: Dict[str, Any]):
                                                                       colour_key)
                     self.base_colours[colour_key] = colour
 
+
     @staticmethod
     def _load_colour_or_gradient_from_theme(theme_colours_dictionary: Dict[str, str],
                                             colour_id: str) -> Union[pygame.Color, ColourGradient]:
@@ -962,57 +970,19 @@ def _load_colour_or_gradient_from_theme(theme_colours_dictionary: Dict[str, str]
         :param theme_colours_dictionary: Part of the theming file data relating to colours.
         :param colour_id: The ID of the colour or gradient to load.
         """
-        loaded_colour_or_gradient = None
+        # loaded_colour_or_gradient = None
         string_data = theme_colours_dictionary[colour_id]
-        if ',' in string_data:
-            # expecting some type of gradient description in string data
-            string_data_list = string_data.split(',')
-
-            try:
-                gradient_direction = int(string_data_list[-1])
-            except ValueError:
-                warnings.warn("Invalid gradient: " + string_data +
-                              " for id:" + colour_id + " in theme file")
+        colour: Optional[Union[pygame.Color, ColourGradient]] = parse_colour_or_gradient_string(string_data)
+        if colour is None:
+            if len(get_commas_outside_enclosing_glyphs(string_data)) > 0: # Was probably meant to be a gradient
+                warnings.warn("Invalid gradient: " + string_data + " for id:" + colour_id + " in theme file")
             else:
-                if len(string_data_list) == 3:
-                    # two colour gradient
-                    try:
-                        colour_1 = premul_col(pygame.Color(string_data_list[0]))
-                        colour_2 = premul_col(pygame.Color(string_data_list[1]))
-                        loaded_colour_or_gradient = ColourGradient(gradient_direction,
-                                                                   colour_1,
-                                                                   colour_2)
-                    except ValueError:
-                        warnings.warn("Invalid gradient: " + string_data +
-                                      " for id:" + colour_id + " in theme file")
-                elif len(string_data_list) == 4:
-                    # three colour gradient
-                    try:
-                        colour_1 = premul_col(pygame.Color(string_data_list[0]))
-                        colour_2 = premul_col(pygame.Color(string_data_list[1]))
-                        colour_3 = premul_col(pygame.Color(string_data_list[2]))
-                        loaded_colour_or_gradient = ColourGradient(gradient_direction,
-                                                                   colour_1,
-                                                                   colour_2,
-                                                                   colour_3)
-                    except ValueError:
-                        warnings.warn("Invalid gradient: " + string_data +
-                                      " for id:" + colour_id + " in theme file")
+                if string_data.startswith("#"):
+                    warnings.warn("Colour hex code: " + string_data + " for id:" + colour_id + " invalid in theme file")
                 else:
-                    warnings.warn("Invalid gradient: " + string_data +
-                                  " for id:" + colour_id + " in theme file")
-        else:
-            try:  # expecting a regular hex colour in string data
-                loaded_colour_or_gradient = premul_col(pygame.Color(string_data))
-            except ValueError:
-                warnings.warn("Colour hex code: " + string_data + " for id:" + colour_id +
-                              " invalid in theme file")
-
-        if loaded_colour_or_gradient is None:
-            # if the colour or gradient data is invalid, return a black default colour
-            loaded_colour_or_gradient = pygame.Color("#000000")
-
-        return loaded_colour_or_gradient
+                    warnings.warn("Invalid Theme Colour: " + string_data + " for id:" + colour_id + " invalid in theme file" )
+            return pygame.Color("#000000")
+        return colour
 
     def set_locale(self, locale: str):
         """
diff --git a/pygame_gui/core/utility.py b/pygame_gui/core/utility.py
index 310b750f..8fa90293 100644
--- a/pygame_gui/core/utility.py
+++ b/pygame_gui/core/utility.py
@@ -616,3 +616,9 @@ def translate(text_to_translate: str, **keywords) -> str:
              is passed back.
     """
     return i18n.t(text_to_translate, **keywords)
+
+
+
+
+
+


Test patch:
diff --git a/tests/test_constants.py b/tests/test_constants.py
index 8aa24644..bec31645 100644
--- a/tests/test_constants.py
+++ b/tests/test_constants.py
@@ -1,6 +1,8 @@
 import pytest
 
 from pygame_gui import UITextEffectType
+from pygame_gui.core.colour_parser import is_valid_colour_string
+from pygame_gui._constants import _namedColours
 
 
 class TestConstants:
@@ -21,3 +23,20 @@ def test_ui_text_effect_type(self):
 
         with pytest.raises(AttributeError):
             text_effect_type + 5
+        
+    def test_colours(self):
+        """The only real requirement for a colour name is that it is not case sensitive.
+        As convention, the colour name should always be stored as a lower cased string, so that any colour name compared to the string can simply be lowered with .lower() for checking validity
+        """
+        errorMessages: list[str] = []
+        for colourName, colourValue in _namedColours.items():
+            if not colourName == colourName.lower():
+                errorMessages.append(f'Found Invalid Colour Name: {colourName} (must be lowercased)')
+            if not is_valid_colour_string(colourValue):
+                errorMessages.append(f'Found Invalid Colour Name Pair: {colourName}, {colourValue}')
+
+        if len(errorMessages) > 0:
+            pytest.fail("\n".join(errorMessages))
+        assert len(errorMessages) == 0
+            
+
diff --git a/tests/test_core/test_colour_parser.py b/tests/test_core/test_colour_parser.py
new file mode 100644
index 00000000..d248de44
--- /dev/null
+++ b/tests/test_core/test_colour_parser.py
@@ -0,0 +1,187 @@
+import pytest
+import pygame
+from pygame_gui.core.colour_parser import parse_degree_string, is_u8_string, is_degree_string, is_percentage_string, parse_colour_or_gradient_string, is_valid_colour_string, is_valid_gradient_string
+
+
+class TestColourParsing:
+    def test_rgb(self, _init_pygame):
+        assert parse_colour_or_gradient_string("rgb(20, 30, 50)") == pygame.Color(20, 30, 50)
+
+    def test_rgb_out_of_range(self, _init_pygame):
+        assert is_valid_colour_string("rgb(-20, 30, 50") is False
+
+    def test_cmy_int(self, _init_pygame):
+        assert is_valid_colour_string("cmy(20, 0.5, 0.3)") is False
+
+    def test_degree_string_unit(self, _init_pygame):
+        assert is_valid_colour_string("hsl(30deg, 0.5, 0.5)") is True
+
+    def test_percentage_and_float(self, _init_pygame):
+        assert is_valid_colour_string("cmy(20%, 0.20, 40%)") is True
+
+    def test_rgb_above_range(self, _init_pygame):
+        assert is_valid_colour_string("rgb(260, 200, 150)") is False
+
+    def test_hex_shorthand(self, _init_pygame):
+        assert is_valid_colour_string("#fa2") is True
+
+    def test_hex_shorthand_alpha(self, _init_pygame):
+        assert is_valid_colour_string("#fa2f") is True
+
+    def test_hex_alpha(self, _init_pygame):
+        assert is_valid_colour_string("#ff2389ad") is True
+
+    def test_hex_capitalized(self, _init_pygame):
+        assert is_valid_colour_string("#FA3CB4") is True
+
+    def test_hex_digit_out_of_range(self, _init_pygame):
+        assert is_valid_colour_string("#FG2R40") is False
+
+    def test_double_gradient(self, _init_pygame):
+        assert is_valid_gradient_string("#446A3B,#23AC67,30") is True
+
+    def test_gradient_degree_unit(self, _init_pygame):
+        assert is_valid_gradient_string("#446A3B,#23AC67,30deg") is True
+
+    def test_gradient_rgb_hex(self, _init_pygame):
+        assert is_valid_gradient_string("rgb(30, 40, 100),#23AC67,30deg") is True
+
+    def test_rgb_double_comma(self, _init_pygame):
+        assert is_valid_colour_string("rgb(30, 40,, 100)") is False
+
+    def test_rgb_double_parentheses(self, _init_pygame):
+        assert is_valid_colour_string("rgb(30, 40,, 100))") is False
+
+    def test_rgb_capitalized(self, _init_pygame):
+        assert is_valid_colour_string("RGB(30, 40, 100)") is True
+
+    def test_cmy_capitalized(self, _init_pygame):
+        assert is_valid_colour_string("CMY(0.30, 0.40, 0.100)") is True
+
+    def test_hsl_capitalized(self, _init_pygame):
+        assert is_valid_colour_string("HSL(30deg, 0.40, 0.100)") is True
+
+    def test_float_without_leading_zeros(self, _init_pygame):
+        assert is_valid_colour_string("cmy(.30, .40, .100)") is True
+
+    def test_triple_gradient(self, _init_pygame):
+        assert is_valid_gradient_string("rgb(20, 30, 40),cmy(0.2, 0.5, 0.7),hsl(40deg, 50%, 70%),30deg") is True
+
+    def test_quadruple_gradient(self, _init_pygame):
+        assert is_valid_gradient_string("rgb(20, 30, 40),cmy(0.2, 0.5, 0.7),hsl(40deg, 50%, 70%),#32FC95,30deg") is False
+
+    def test_no_colour_gradient(self, _init_pygame):
+        assert is_valid_gradient_string(",30deg") is False
+
+    def test_reverse_order_gradient(self, _init_pygame):
+        assert is_valid_gradient_string("30deg,#222,#888") is False
+
+    def test_misspelled_degree_unit(self, _init_pygame):
+        assert is_valid_colour_string("hsl(30deb, 0.2, 0.4)") is False
+
+    def test_nested_colour(self, _init_pygame):
+        assert is_valid_colour_string("rgb(rgb(0, 5, 3), 11, 22)") is False
+
+    def test_double_opening_parentheses(self, _init_pygame):
+        assert is_valid_colour_string("rgb((0, 30, 50)") is False
+
+    def test_leading_comma_in_parentheses(self, _init_pygame):
+        assert is_valid_colour_string("cmy(,0.3, 0.4, 0.5)") is False
+
+    def test_trailing_comma_in_parentheses(self, _init_pygame):
+        assert is_valid_colour_string("hsl(40, 0.5, 0.6,)") is False
+
+    def test_trailing_gradient_comma(self, _init_pygame):
+        assert is_valid_colour_string("#222,#ccc,45deg,") is False
+
+    def test_hex_shorthand_alpha_parsing(self, _init_pygame):
+        assert (parse_colour_or_gradient_string("#ffff") == pygame.Color(255, 255, 255, 255)) is True
+
+    def test_hex_shorthand_parsing(self, _init_pygame):
+        assert (parse_colour_or_gradient_string("#fff") == pygame.Color(255, 255, 255)) is True
+
+    def test_rgba_validity(self, _init_pygame):
+        assert is_valid_colour_string("rgba(0, 20, 40, 30)") is True
+
+    def test_rgb_too_many_components(self, _init_pygame):
+        assert is_valid_colour_string("rgb(0, 30, 50, 70)") is False
+
+    def test_rgb_too_little_components(self, _init_pygame):
+        assert is_valid_colour_string("rgb(0, 30)") is False
+
+    def test_reversed_parentheses(self, _init_pygame):
+        assert is_valid_colour_string("rgb)0, 40, 50(") is False
+
+    def test_reversed_parentheses_gradient(self, _init_pygame):
+        assert is_valid_gradient_string("rgb)0, 40, 50(,#fff,40") is False
+
+    def test_hsv_string(self, _init_pygame):
+        assert is_valid_colour_string("hsv(30deg, 0.4, 40%)") is True
+
+    def test_hsva_string(self, _init_pygame):
+        assert is_valid_colour_string("hsva(40, 40%, 20%, 70%)") is True
+
+    def test_hsva_string(self, _init_pygame):
+        assert is_valid_colour_string("hsla(160deg, 72%, 25%, 32%)") is True
+
+    def test_space_separated(self, _init_pygame):
+        assert is_valid_colour_string("rgb(20 20 20)") is False
+
+    def test_space_and_comma_separated(self, _init_pygame):
+        assert is_valid_colour_string("rgb(20, 20 20") is False
+
+    def test_spaces_in_gradient(self, _init_pygame):
+        assert is_valid_gradient_string("#f2f, hsv(30, 0.5, 0.8), 50deg") is True
+
+    def test_whitespace_at_start(self, _init_pygame):
+        assert is_valid_colour_string("   rgb(20, 50, 80)") is False
+
+    def test_whitespace_at_end(self, _init_pygame):
+        assert is_valid_colour_string("rgb(20, 50, 80)    ") is False
+
+    def test_whitespace_on_ends(self, _init_pygame):
+        assert is_valid_colour_string("   rgb(20, 50, 80)    ") is False
+
+    def test_rgb_floats(self, _init_pygame):
+        assert is_valid_colour_string("rgb(3.5, 70, 100)") is False
+
+    def test_is_u8_above_range(self, _init_pygame):
+        assert is_u8_string("257") is False
+
+    def test_is_u8_below_range(self, _init_pygame):
+        assert is_u8_string("-2") is False
+
+    def test_is_u8_on_bounds(self, _init_pygame):
+        assert is_u8_string("0") is True and is_u8_string("255") is True and is_u8_string("-1") is False and is_u8_string("257") is False
+
+    def test_u8_float(self, _init_pygame):
+        assert is_u8_string("4.3") is False
+
+    def test_degree_on_bounds(self, _init_pygame):
+        assert is_degree_string("0") is True and is_degree_string("360") is True and is_degree_string("-1") is False and is_degree_string("361") is False
+
+    def test_degree_unit(self, _init_pygame):
+        assert is_degree_string("40deg") is True
+    
+    def test_degree_only_unit(self, _init_pygame):
+        assert is_degree_string("deg") is False
+    
+    def test_percentage_only_sign(self, _init_pygame):
+        assert is_percentage_string("%") is False
+
+    def test_percentage_float_and_sign(self, _init_pygame):
+        assert is_percentage_string("37.5%") is False
+
+    def test_degree_unit_optionality(self, _init_pygame):
+        assert parse_degree_string("40") == parse_degree_string("40deg")
+
+    def test_gradient_colour_names(self, _init_pygame):
+        assert is_valid_gradient_string("red,blue,40") is True
+
+    def test_gradient_colour_names_three(self, _init_pygame):
+        assert is_valid_gradient_string("red,green,blue,60deg") is True
+
+
+
+if __name__ == "__main__":
+    pytest.console_main()
\ No newline at end of file
diff --git a/tests/test_elements/test_ui_button.py b/tests/test_elements/test_ui_button.py
index ef559c8a..8e62d6dc 100644
--- a/tests/test_elements/test_ui_button.py
+++ b/tests/test_elements/test_ui_button.py
@@ -649,6 +649,7 @@ def test_rebuild_from_changed_theme_data_non_default(self, _init_pygame,
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600),
diff --git a/tests/test_elements/test_ui_drop_down_menu.py b/tests/test_elements/test_ui_drop_down_menu.py
index 3ab38d92..9d45609b 100644
--- a/tests/test_elements/test_ui_drop_down_menu.py
+++ b/tests/test_elements/test_ui_drop_down_menu.py
@@ -248,6 +248,7 @@ def test_rebuild_from_theme_data_non_default(self, _init_pygame,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
@@ -261,6 +262,7 @@ def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values_live(self, _init_pygame,
                                                      _display_surface_return_none):
         manager = UIManager((800, 600))
diff --git a/tests/test_elements/test_ui_horizontal_scroll_bar.py b/tests/test_elements/test_ui_horizontal_scroll_bar.py
index 1a14f448..c4cbc45c 100644
--- a/tests/test_elements/test_ui_horizontal_scroll_bar.py
+++ b/tests/test_elements/test_ui_horizontal_scroll_bar.py
@@ -177,6 +177,7 @@ def test_rebuild_from_theme_data_no_arrow_buttons(self, _init_pygame,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_horizontal_slider.py b/tests/test_elements/test_ui_horizontal_slider.py
index 6fdc9e91..7a9a29f4 100644
--- a/tests/test_elements/test_ui_horizontal_slider.py
+++ b/tests/test_elements/test_ui_horizontal_slider.py
@@ -175,6 +175,7 @@ def test_rebuild_from_theme_data_no_arrow_buttons(self, _init_pygame,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests",
diff --git a/tests/test_elements/test_ui_label.py b/tests/test_elements/test_ui_label.py
index f13aafbd..70114044 100644
--- a/tests/test_elements/test_ui_label.py
+++ b/tests/test_elements/test_ui_label.py
@@ -110,6 +110,7 @@ def test_rebuild_from_theme_data_non_default_3(self, _init_pygame):
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Label Rect is too small for text")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes",
                                                      "ui_label_bad_values.json"))
diff --git a/tests/test_elements/test_ui_panel.py b/tests/test_elements/test_ui_panel.py
index 7f61d714..295de66c 100644
--- a/tests/test_elements/test_ui_panel.py
+++ b/tests/test_elements/test_ui_panel.py
@@ -429,6 +429,7 @@ def test_rebuild_from_changed_theme_data_non_default(self, _init_pygame,
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
     @pytest.mark.filterwarnings("ignore:Unable to load")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes",
diff --git a/tests/test_elements/test_ui_screen_space_health_bar.py b/tests/test_elements/test_ui_screen_space_health_bar.py
index 63795809..f2651508 100644
--- a/tests/test_elements/test_ui_screen_space_health_bar.py
+++ b/tests/test_elements/test_ui_screen_space_health_bar.py
@@ -111,6 +111,7 @@ def test_rebuild_from_theme_data_non_default(self, _init_pygame):
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes", "ui_screen_health_bar_bad_values.json"))
         healthy_sprite = HealthySprite()
diff --git a/tests/test_elements/test_ui_selection_list.py b/tests/test_elements/test_ui_selection_list.py
index 91161956..17b031ab 100644
--- a/tests/test_elements/test_ui_selection_list.py
+++ b/tests/test_elements/test_ui_selection_list.py
@@ -656,6 +656,7 @@ def test_rebuild_from_changed_theme_data_non_default(self, _init_pygame,
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
     @pytest.mark.filterwarnings("ignore:Unable to load")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes",
diff --git a/tests/test_elements/test_ui_status_bar.py b/tests/test_elements/test_ui_status_bar.py
index 9e032047..1cd8804d 100644
--- a/tests/test_elements/test_ui_status_bar.py
+++ b/tests/test_elements/test_ui_status_bar.py
@@ -51,6 +51,7 @@ def test_rebuild_from_theme_data_non_default(self, _init_pygame):
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes", "ui_status_bar_bad_values.json"))
         healthy_sprite = HealthySprite()
diff --git a/tests/test_elements/test_ui_text_box.py b/tests/test_elements/test_ui_text_box.py
index a7a07b56..7319519b 100644
--- a/tests/test_elements/test_ui_text_box.py
+++ b/tests/test_elements/test_ui_text_box.py
@@ -622,6 +622,7 @@ def test_rebuild_from_theme_data_non_default(self, _init_pygame,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_text_entry_box.py b/tests/test_elements/test_ui_text_entry_box.py
index 9fb32ea7..069fa143 100644
--- a/tests/test_elements/test_ui_text_entry_box.py
+++ b/tests/test_elements/test_ui_text_entry_box.py
@@ -578,6 +578,7 @@ def test_rebuild_from_theme_data_non_default(self, _init_pygame,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
@@ -822,6 +823,7 @@ def test_set_text_rebuild_select_area_3(self, _init_pygame):
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_set_text_rebuild_select_area_3(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
@@ -1316,6 +1318,7 @@ def test_process_event_backspace_select_range(self, _init_pygame: None,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_redraw_selected_text(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
@@ -1427,6 +1430,7 @@ def test_update_cursor_blink(self,  _init_pygame, _display_surface_return_none:
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
diff --git a/tests/test_elements/test_ui_text_entry_line.py b/tests/test_elements/test_ui_text_entry_line.py
index 896b84a5..ebb88382 100644
--- a/tests/test_elements/test_ui_text_entry_line.py
+++ b/tests/test_elements/test_ui_text_entry_line.py
@@ -211,6 +211,7 @@ def test_set_text_rebuild_select_area_3(self, _init_pygame):
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_set_text_rebuild_select_area_3(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
@@ -897,6 +898,7 @@ def test_set_forbidden_characters_invalid_id(self, _init_pygame, default_ui_mana
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_redraw_selected_text(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
@@ -1020,6 +1022,7 @@ def test_rebuild_from_theme_data_non_default(self, _init_pygame):
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data",
                                                      "themes",
diff --git a/tests/test_elements/test_ui_vertical_scroll_bar.py b/tests/test_elements/test_ui_vertical_scroll_bar.py
index 346287e9..160f7387 100644
--- a/tests/test_elements/test_ui_vertical_scroll_bar.py
+++ b/tests/test_elements/test_ui_vertical_scroll_bar.py
@@ -170,6 +170,7 @@ def test_rebuild_from_theme_data_no_arrow_buttons(self, _init_pygame,
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame,
                                                 _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_window.py b/tests/test_elements/test_ui_window.py
index f0a9e43f..3d25d105 100644
--- a/tests/test_elements/test_ui_window.py
+++ b/tests/test_elements/test_ui_window.py
@@ -433,6 +433,7 @@ def test_rebuild_from_changed_theme_data_no_title_bar(self, _init_pygame,
     @pytest.mark.filterwarnings("ignore:Colour hex code")
     @pytest.mark.filterwarnings("ignore:Invalid gradient")
     @pytest.mark.filterwarnings("ignore:Unable to load")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_changed_theme_data_bad_values(self, _init_pygame,
                                                         _display_surface_return_none):
         manager = UIManager((800, 600), os.path.join("tests", "data",
diff --git a/tests/test_elements/test_ui_world_space_health_bar.py b/tests/test_elements/test_ui_world_space_health_bar.py
index 64781f8e..a037581d 100644
--- a/tests/test_elements/test_ui_world_space_health_bar.py
+++ b/tests/test_elements/test_ui_world_space_health_bar.py
@@ -70,6 +70,7 @@ def test_rebuild_from_theme_data_non_default(self, _init_pygame):
 
     @pytest.mark.filterwarnings("ignore:Invalid value")
     @pytest.mark.filterwarnings("ignore:Colour hex code")
+    @pytest.mark.filterwarnings("ignore:Invalid Theme Colour")
     def test_rebuild_from_theme_data_bad_values(self, _init_pygame):
         manager = UIManager((800, 600), os.path.join("tests", "data", "themes", "ui_world_health_bar_bad_values.json"))
         healthy_sprite = UIWorldSpaceHealthBar.ExampleHealthSprite()