mirror of
https://github.com/Xevion/tcp-chat.git
synced 2025-12-06 15:16:43 -06:00
new Color class replacing bare hex strings, WCAG contrast ratio and relative luminance functions, allow automatic selection of more ideal contrasting colors
This commit is contained in:
117
constants.py
117
constants.py
@@ -1,3 +1,7 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
import webcolors
|
||||||
|
|
||||||
HEADER_LENGTH = 10
|
HEADER_LENGTH = 10
|
||||||
|
|
||||||
|
|
||||||
@@ -24,17 +28,118 @@ class Requests:
|
|||||||
GET_HISTORY = 'GET_HISTORY' # Send a short history of the chat to the client
|
GET_HISTORY = 'GET_HISTORY' # Send a short history of the chat to the client
|
||||||
|
|
||||||
|
|
||||||
|
class Color():
|
||||||
|
"""
|
||||||
|
Describes a basic RGB color.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name: str, hex: str) -> None:
|
||||||
|
self.name, self.hex = name, hex
|
||||||
|
self.rgb = webcolors.hex_to_rgb(hex)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'Color({self.name})'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> str:
|
||||||
|
return '_'.join(self.name.split()).upper()
|
||||||
|
|
||||||
|
def relative_luminance(self) -> float:
|
||||||
|
"""
|
||||||
|
Calculates the relative luminance of a given color, according to WCAG guidelines.
|
||||||
|
|
||||||
|
https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||||
|
|
||||||
|
:return: The relative luminance of this color.
|
||||||
|
"""
|
||||||
|
r, g, b = self.rgb.red / 255, self.rgb.green / 255, self.rgb.blue / 255
|
||||||
|
r = r / 12.92 if r <= 0.03928 else ((r + 0.055) / 1.055) ** 2.4
|
||||||
|
g = g / 12.92 if g <= 0.03928 else ((g + 0.055) / 1.055) ** 2.4
|
||||||
|
b = b / 12.92 if b <= 0.03928 else ((b + 0.055) / 1.055) ** 2.4
|
||||||
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b
|
||||||
|
|
||||||
|
def contrast_ratio(self, other: 'Color') -> float:
|
||||||
|
"""
|
||||||
|
Calculates the relative contrast between two colors in a ratio from 1:1 to 21:1, according to WCAG guidelines.
|
||||||
|
|
||||||
|
https://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
||||||
|
|
||||||
|
:param other: The darker color.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
l1 = self.relative_luminance()
|
||||||
|
l2 = other.relative_luminance()
|
||||||
|
return (l1 + 0.05) / (l2 + 0.05)
|
||||||
|
|
||||||
|
|
||||||
class Colors:
|
class Colors:
|
||||||
"""
|
"""
|
||||||
Stores hexadecimal color codes of popular colors, with names.
|
Stores hexadecimal color codes of popular colors, with names.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ALL_NAMES = ['Aqua', 'Azure', 'Beige', 'Black', 'Blue', 'Brown', 'Cyan', 'Dark Blue', 'Dark Cyan', 'Dark Grey',
|
AQUA = Color("Aqua", "#00ffff")
|
||||||
'Dark Green', 'Dark Khaki', 'Dark Magenta', 'Dark Olive Green', 'Dark Orange', ' Dark Orchid', 'Dark Red', 'Dark Salmon',
|
AZURE = Color("Azure", "#f0ffff")
|
||||||
'Dark Violet', 'Fuchsia', 'Gold', 'Green', 'Indigo', 'Khaki', 'Light Blue', 'Light Cyan',
|
BEIGE = Color("Beige", "#f5f5dc")
|
||||||
'Light Green', 'Light Grey', 'Light Pink', 'Light Yellow', 'Lime', 'Magenta', 'Maroon', 'Navy',
|
BLACK = Color("Black", "#000000")
|
||||||
'Olive', 'Orange', 'Pink', 'Purple', 'Violet', 'Red', 'Silver', 'White', 'Yellow']
|
BLUE = Color("Blue", "#0000ff")
|
||||||
|
BROWN = Color("Brown", "#a52a2a")
|
||||||
|
CYAN = Color("Cyan", "#00ffff")
|
||||||
|
DARKBLUE = Color("Dark Blue", "#00008b")
|
||||||
|
DARKCYAN = Color("Dark Cyan", "#008b8b")
|
||||||
|
DARKGREY = Color("Dark Grey", "#a9a9a9")
|
||||||
|
DARKGREEN = Color("Dark Green", "#006400")
|
||||||
|
DARKKHAKI = Color("Dark Khaki", "#bdb76b")
|
||||||
|
DARKMAGENTA = Color("Dark Magenta", "#8b008b")
|
||||||
|
DARKOLIVEGREEN = Color("Dark Olive Green", "#556b2f")
|
||||||
|
DARKORANGE = Color("Dark Orange", "#ff8c00")
|
||||||
|
DARKORCHID = Color("Dark Orchid", "#9932cc")
|
||||||
|
DARKRED = Color("Dark Red", "#8b0000")
|
||||||
|
DARKSALMON = Color("Dark Salmon", "#e9967a")
|
||||||
|
DARKVIOLET = Color("Dark Violet", "#9400d3")
|
||||||
|
FUCHSIA = Color("Fuchsia", "#ff00ff")
|
||||||
|
GOLD = Color("Gold", "#ffd700")
|
||||||
|
GREEN = Color("Green", "#008000")
|
||||||
|
INDIGO = Color("Indigo", "#4b0082")
|
||||||
|
KHAKI = Color("Khaki", "#f0e68c")
|
||||||
|
LIGHTBLUE = Color("Light Blue", "#add8e6")
|
||||||
|
LIGHTCYAN = Color("Light Cyan", "#e0ffff")
|
||||||
|
LIGHTGREEN = Color("Light Green", "#90ee90")
|
||||||
|
LIGHTGREY = Color("Light Grey", "#d3d3d3")
|
||||||
|
LIGHTPINK = Color("Light Pink", "#ffb6c1")
|
||||||
|
LIGHTYELLOW = Color("Light Yellow", "#ffffe0")
|
||||||
|
LIME = Color("Lime", "#00ff00")
|
||||||
|
MAGENTA = Color("Magenta", "#ff00ff")
|
||||||
|
MAROON = Color("Maroon", "#800000")
|
||||||
|
NAVY = Color("Navy", "#000080")
|
||||||
|
OLIVE = Color("Olive", "#808000")
|
||||||
|
ORANGE = Color("Orange", "#ffa500")
|
||||||
|
PINK = Color("Pink", "#ffc0cb")
|
||||||
|
PURPLE = Color("Purple", "#800080")
|
||||||
|
VIOLET = Color("Violet", "#800080")
|
||||||
|
RED = Color("Red", "#ff0000")
|
||||||
|
SILVER = Color("Silver", "#c0c0c0")
|
||||||
|
WHITE = Color("White", "#ffffff")
|
||||||
|
YELLOW = Color("Yellow", "#ffff00")
|
||||||
|
|
||||||
ALL = [AQUA, AZURE, BEIGE, BLACK, BLUE, BROWN, CYAN, DARKBLUE, DARKCYAN, DARKGREY, DARKGREEN, DARKKHAKI,
|
ALL = [AQUA, AZURE, BEIGE, BLACK, BLUE, BROWN, CYAN, DARKBLUE, DARKCYAN, DARKGREY, DARKGREEN, DARKKHAKI,
|
||||||
DARKMAGENTA, DARKOLIVEGREEN, DARKORANGE, DARKORCHID, DARKRED, DARKSALMON, DARKVIOLET, FUCHSIA, GOLD, GREEN,
|
DARKMAGENTA, DARKOLIVEGREEN, DARKORANGE, DARKORCHID, DARKRED, DARKSALMON, DARKVIOLET, FUCHSIA, GOLD, GREEN,
|
||||||
INDIGO, KHAKI, LIGHTBLUE, LIGHTCYAN, LIGHTGREEN, LIGHTGREY, LIGHTPINK, LIGHTYELLOW, LIME, MAGENTA, MAROON,
|
INDIGO, KHAKI, LIGHTBLUE, LIGHTCYAN, LIGHTGREEN, LIGHTGREY, LIGHTPINK, LIGHTYELLOW, LIME, MAGENTA, MAROON,
|
||||||
NAVY, OLIVE, ORANGE, PINK, PURPLE, VIOLET, RED, SILVER, WHITE, YELLOW, ]
|
NAVY, OLIVE, ORANGE, PINK, PURPLE, VIOLET, RED, SILVER, WHITE, YELLOW]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def has_contrast(ratio: float, background: Color = WHITE) -> List[Color]:
|
||||||
|
"""
|
||||||
|
Returns a list of Color objects with contrast ratios above the given minimum.
|
||||||
|
|
||||||
|
:param ratio: The minimum contrast ratio each color must adhere to.
|
||||||
|
:param background: Defaults to White, the background (lighter) for the other contrasting colors to be compared to.
|
||||||
|
:return: A list of Color objects with contrast ratios above the given minimum.
|
||||||
|
"""
|
||||||
|
above_ratio = []
|
||||||
|
|
||||||
|
for color in Colors.ALL:
|
||||||
|
contrast = background.contrast_ratio(color)
|
||||||
|
if contrast >= ratio:
|
||||||
|
above_ratio.append(color)
|
||||||
|
|
||||||
|
return above_ratio
|
||||||
|
|||||||
@@ -70,14 +70,20 @@ class CommandHandler:
|
|||||||
logger.error(f'Could not process client {self.client.nickname}\'s command request.', exc_info=e)
|
logger.error(f'Could not process client {self.client.nickname}\'s command request.', exc_info=e)
|
||||||
return 'A fatal error occurred while trying to process this command.'
|
return 'A fatal error occurred while trying to process this command.'
|
||||||
|
|
||||||
def reroll(self) -> str:
|
def reroll(self, minimum_contrast: float = 4.65) -> str:
|
||||||
"""
|
"""
|
||||||
Randomly change the client's color to a different color.
|
Randomly change the client's color to a different color.
|
||||||
"""
|
"""
|
||||||
newColor = random.choice(constants.Colors.ALL)
|
i = 0
|
||||||
newColorName = constants.Colors.ALL_NAMES[constants.Colors.ALL.index(newColor)]
|
newColor = self.client.color
|
||||||
|
choices = constants.Colors.has_contrast(float(minimum_contrast))
|
||||||
|
|
||||||
|
while i < 50 and newColor == self.client.color:
|
||||||
|
newColor = random.choice(choices)
|
||||||
|
|
||||||
self.client.color = newColor
|
self.client.color = newColor
|
||||||
return f'Changed your color to {newColorName} ({newColor})'
|
contrast = round(constants.Colors.WHITE.contrast_ratio(newColor), 1)
|
||||||
|
return f'Changed your color to {newColor.name} ({newColor.hex}/{contrast})'
|
||||||
|
|
||||||
def help(self, command: str) -> Optional[str]:
|
def help(self, command: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Client:
|
|||||||
self.conn.send(helpers.prepare_json(
|
self.conn.send(helpers.prepare_json(
|
||||||
{
|
{
|
||||||
'type': constants.Types.USER_LIST,
|
'type': constants.Types.USER_LIST,
|
||||||
'users': [{'nickname': other.nickname, 'color': other.color} for other in self.all_clients]
|
'users': [{'nickname': other.nickname, 'color': other.color.hex} for other in self.all_clients]
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -69,13 +69,13 @@ class Client:
|
|||||||
def send_message(self, message: str) -> None:
|
def send_message(self, message: str) -> None:
|
||||||
"""Sends a string message as the server to this client."""
|
"""Sends a string message as the server to this client."""
|
||||||
self.conn.send(helpers.prepare_message(
|
self.conn.send(helpers.prepare_message(
|
||||||
nickname='Server', message=message, color=constants.Colors.BLACK
|
nickname='Server', message=message, color=constants.Colors.BLACK.hex
|
||||||
))
|
))
|
||||||
|
|
||||||
def broadcast_message(self, message: str) -> None:
|
def broadcast_message(self, message: str) -> None:
|
||||||
"""Sends a string message to all connected clients as the Server."""
|
"""Sends a string message to all connected clients as the Server."""
|
||||||
prepared = helpers.prepare_message(
|
prepared = helpers.prepare_message(
|
||||||
nickname='Server', message=message, color=constants.Colors.BLACK
|
nickname='Server', message=message, color=constants.Colors.BLACK.hex
|
||||||
)
|
)
|
||||||
for client in self.all_clients:
|
for client in self.all_clients:
|
||||||
client.send(prepared)
|
client.send(prepared)
|
||||||
@@ -99,7 +99,7 @@ class Client:
|
|||||||
self.broadcast(helpers.prepare_message(
|
self.broadcast(helpers.prepare_message(
|
||||||
nickname=self.nickname,
|
nickname=self.nickname,
|
||||||
message=data['content'],
|
message=data['content'],
|
||||||
color=self.color
|
color=self.color.hex
|
||||||
))
|
))
|
||||||
|
|
||||||
command = data['content'].strip()
|
command = data['content'].strip()
|
||||||
|
|||||||
Reference in New Issue
Block a user