mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-04 14:33:28 +08:00
Merge pull request #671 from 3alTemp/hex-color-pick
HEX support for advanced color picker
This commit is contained in:
commit
f62d0e26e0
@ -40,6 +40,7 @@
|
|||||||
EraOSBeta!)
|
EraOSBeta!)
|
||||||
- Added a UI for customizing Series Length in Teams and Points-to-Win in FFA
|
- Added a UI for customizing Series Length in Teams and Points-to-Win in FFA
|
||||||
(Thanks EraOSBeta!)
|
(Thanks EraOSBeta!)
|
||||||
|
- Implemented HEX code support to the advanced color picker (Thanks 3alTemp!)
|
||||||
- Players leaving the game after getting hurt will now grant kills. (Thanks
|
- Players leaving the game after getting hurt will now grant kills. (Thanks
|
||||||
Temp!)
|
Temp!)
|
||||||
- Sphinx based Python documentation generation is now wired up (Thanks
|
- Sphinx based Python documentation generation is now wired up (Thanks
|
||||||
|
|||||||
@ -213,6 +213,8 @@ class ColorPickerExact(PopupWindow):
|
|||||||
self._last_press_time = bui.apptime()
|
self._last_press_time = bui.apptime()
|
||||||
self._last_press_color_name: str | None = None
|
self._last_press_color_name: str | None = None
|
||||||
self._last_press_increasing: bool | None = None
|
self._last_press_increasing: bool | None = None
|
||||||
|
self._hex_timer: bui.AppTimer | None = None
|
||||||
|
self._hex_prev_text: str = '#FFFFFF'
|
||||||
self._change_speed = 1.0
|
self._change_speed = 1.0
|
||||||
width = 180.0
|
width = 180.0
|
||||||
height = 240.0
|
height = 240.0
|
||||||
@ -229,11 +231,25 @@ class ColorPickerExact(PopupWindow):
|
|||||||
)
|
)
|
||||||
self._swatch = bui.imagewidget(
|
self._swatch = bui.imagewidget(
|
||||||
parent=self.root_widget,
|
parent=self.root_widget,
|
||||||
position=(width * 0.5 - 50, height - 70),
|
position=(width * 0.5 - 65 + 5, height - 95),
|
||||||
size=(100, 70),
|
size=(130, 115),
|
||||||
texture=bui.gettexture('buttonSquare'),
|
texture=bui.gettexture('clayStroke'),
|
||||||
color=(1, 0, 0),
|
color=(1, 0, 0),
|
||||||
)
|
)
|
||||||
|
self._hex_textbox = bui.textwidget(
|
||||||
|
parent=self.root_widget,
|
||||||
|
position=(width * 0.5 - 37.5 + 3, height - 51),
|
||||||
|
max_chars=9,
|
||||||
|
text='#FFFFFF',
|
||||||
|
autoselect=True,
|
||||||
|
size=(75, 30),
|
||||||
|
v_align='center',
|
||||||
|
editable=True,
|
||||||
|
maxwidth=70,
|
||||||
|
allow_clear_button=False,
|
||||||
|
force_internal_editing=True,
|
||||||
|
)
|
||||||
|
|
||||||
x = 50
|
x = 50
|
||||||
y = height - 90
|
y = height - 90
|
||||||
self._label_r: bui.Widget
|
self._label_r: bui.Widget
|
||||||
@ -288,6 +304,37 @@ class ColorPickerExact(PopupWindow):
|
|||||||
# color to the delegate, so start doing that.
|
# color to the delegate, so start doing that.
|
||||||
self._update_for_color()
|
self._update_for_color()
|
||||||
|
|
||||||
|
# Update our HEX stuff!
|
||||||
|
self._update_for_hex()
|
||||||
|
self._hex_timer = bui.AppTimer(0.025, self._update_for_hex, repeat=True)
|
||||||
|
|
||||||
|
def _update_for_hex(self) -> None:
|
||||||
|
"""Update for any HEX or color change."""
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
hextext = cast(str, bui.textwidget(query=self._hex_textbox))
|
||||||
|
hexcolor: tuple
|
||||||
|
# Check if our current hex text doesn't match with our old one.
|
||||||
|
# Convert our current hex text into a color if possible.
|
||||||
|
if hextext != self._hex_prev_text:
|
||||||
|
try:
|
||||||
|
hexcolor = hex_to_color(hextext)
|
||||||
|
if len(hexcolor) == 4:
|
||||||
|
r, g, b, a = hexcolor
|
||||||
|
del a # unused
|
||||||
|
else:
|
||||||
|
r, g, b = hexcolor
|
||||||
|
# Replace the color!
|
||||||
|
for i, ch in enumerate((r, g, b)):
|
||||||
|
self._color[i] = max(0.0, min(1.0, ch))
|
||||||
|
self._update_for_color()
|
||||||
|
# Usually, a ValueError will occur if the provided hex
|
||||||
|
# is incomplete, which occurs when in the midst of typing it.
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
# Store the current text for our next comparison.
|
||||||
|
self._hex_prev_text = hextext
|
||||||
|
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
def _update_for_color(self) -> None:
|
def _update_for_color(self) -> None:
|
||||||
if not self.root_widget:
|
if not self.root_widget:
|
||||||
@ -303,6 +350,16 @@ class ColorPickerExact(PopupWindow):
|
|||||||
if self._delegate is not None:
|
if self._delegate is not None:
|
||||||
self._delegate.color_picker_selected_color(self, self._color)
|
self._delegate.color_picker_selected_color(self, self._color)
|
||||||
|
|
||||||
|
# Show the HEX code of this color.
|
||||||
|
r, g, b = self._color
|
||||||
|
hexcode = color_to_hex(r, g, b, None)
|
||||||
|
self._hex_prev_text = hexcode
|
||||||
|
bui.textwidget(
|
||||||
|
edit=self._hex_textbox,
|
||||||
|
text=hexcode,
|
||||||
|
color=color_overlay_func(r, g, b),
|
||||||
|
)
|
||||||
|
|
||||||
def _color_change_press(self, color_name: str, increasing: bool) -> None:
|
def _color_change_press(self, color_name: str, increasing: bool) -> None:
|
||||||
# If we get rapid-fire presses, eventually start moving faster.
|
# If we get rapid-fire presses, eventually start moving faster.
|
||||||
current_time = bui.apptime()
|
current_time = bui.apptime()
|
||||||
@ -331,6 +388,8 @@ class ColorPickerExact(PopupWindow):
|
|||||||
return self._tag
|
return self._tag
|
||||||
|
|
||||||
def _transition_out(self) -> None:
|
def _transition_out(self) -> None:
|
||||||
|
# Kill our timer
|
||||||
|
self._hex_timer = None
|
||||||
if not self._transitioning_out:
|
if not self._transitioning_out:
|
||||||
self._transitioning_out = True
|
self._transitioning_out = True
|
||||||
if self._delegate is not None:
|
if self._delegate is not None:
|
||||||
@ -342,3 +401,101 @@ class ColorPickerExact(PopupWindow):
|
|||||||
if not self._transitioning_out:
|
if not self._transitioning_out:
|
||||||
bui.getsound('swish').play()
|
bui.getsound('swish').play()
|
||||||
self._transition_out()
|
self._transition_out()
|
||||||
|
|
||||||
|
|
||||||
|
def hex_to_color(hex_color: str) -> tuple:
|
||||||
|
"""Transforms an RGB / RGBA hex code into an rgb1/rgba1 tuple.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hex_color (str): The HEX color.
|
||||||
|
Raises:
|
||||||
|
ValueError: If the provided HEX color isn't 6 or 8 characters long.
|
||||||
|
Returns:
|
||||||
|
tuple: The color tuple divided by 255.
|
||||||
|
"""
|
||||||
|
# Remove the '#' from the string if provided.
|
||||||
|
if hex_color.startswith('#'):
|
||||||
|
hex_color = hex_color.lstrip('#')
|
||||||
|
# Check if this has a valid length.
|
||||||
|
hexlength = len(hex_color)
|
||||||
|
if not hexlength in [6, 8]:
|
||||||
|
raise ValueError(f'Invalid HEX color provided: "{hex_color}"')
|
||||||
|
|
||||||
|
# Convert the hex bytes to their true byte form.
|
||||||
|
ar, ag, ab, aa = (
|
||||||
|
(int.from_bytes(bytes.fromhex(hex_color[0:2]))),
|
||||||
|
(int.from_bytes(bytes.fromhex(hex_color[2:4]))),
|
||||||
|
(int.from_bytes(bytes.fromhex(hex_color[4:6]))),
|
||||||
|
(int.from_bytes(bytes.fromhex(hex_color[6:8])))
|
||||||
|
if hexlength == 8
|
||||||
|
else None,
|
||||||
|
)
|
||||||
|
# Divide all numbers by 255 and return.
|
||||||
|
nr, ng, nb, na = (
|
||||||
|
x / 255 if x is not None
|
||||||
|
else None for x in (ar, ag, ab, aa)
|
||||||
|
)
|
||||||
|
return (nr, ng, nb, na) if aa is not None else (nr, ng, nb)
|
||||||
|
|
||||||
|
|
||||||
|
def color_to_hex(r: float, g: float, b: float, a: float | None = 1.0) -> str:
|
||||||
|
"""Converts an rgb1 tuple to a HEX color code.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
r (float): Red.
|
||||||
|
g (float): Green.
|
||||||
|
b (float): Blue.
|
||||||
|
a (float, optional): Alpha. Defaults to 1.0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The hexified rgba values.
|
||||||
|
"""
|
||||||
|
# Turn our rgb1 to rgb255
|
||||||
|
nr, ng, nb, na = [
|
||||||
|
int(min(255, x * 255)) if x is not None else x for x in [r, g, b, a]
|
||||||
|
]
|
||||||
|
# Merge all values into their HEX representation.
|
||||||
|
hex_code = (
|
||||||
|
f'#{nr:02x}{ng:02x}{nb:02x}{na:02x}'
|
||||||
|
if na is not None
|
||||||
|
else f'#{nr:02x}{ng:02x}{nb:02x}'
|
||||||
|
)
|
||||||
|
return hex_code
|
||||||
|
|
||||||
|
|
||||||
|
def color_overlay_func(
|
||||||
|
r: float, g: float, b: float, a: float | None = None
|
||||||
|
) -> tuple:
|
||||||
|
"""I could NOT come up with a better function name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
r (float): Red.
|
||||||
|
g (float): Green.
|
||||||
|
b (float): Blue.
|
||||||
|
a (float | None, optional): Alpha. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: A brighter color if the provided one is dark,
|
||||||
|
and a darker one if it's darker.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Calculate the relative luminance using the formula for sRGB
|
||||||
|
# https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||||
|
def relative_luminance(color: float) -> Any:
|
||||||
|
if color <= 0.03928:
|
||||||
|
return color / 12.92
|
||||||
|
return ((color + 0.055) / 1.055) ** 2.4
|
||||||
|
|
||||||
|
luminance = (
|
||||||
|
0.2126 * relative_luminance(r)
|
||||||
|
+ 0.7152 * relative_luminance(g)
|
||||||
|
+ 0.0722 * relative_luminance(b)
|
||||||
|
)
|
||||||
|
# Set our color multiplier depending on the provided color's luminance.
|
||||||
|
luminant = 1.65 if luminance < 0.33 else 0.2
|
||||||
|
# Multiply our given numbers, making sure
|
||||||
|
# they don't blend in the original bg.
|
||||||
|
avg = (0.7 - (r + g + b / 3)) + 0.15
|
||||||
|
r, g, b = [max(avg, x * luminant) for x in (r, g, b)]
|
||||||
|
# Include our alpha and ship it!
|
||||||
|
return (r, g, b, a) if a is not None else (r, g, b)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user