From 1c851e86f6d80bb23e0f6318098360c3fed25a8f Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 12:32:24 -0400 Subject: [PATCH 01/17] Create colors module --- ppb/__init__.py | 3 ++- ppb/colors.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 ppb/colors.py diff --git a/ppb/__init__.py b/ppb/__init__.py index 7da0bad2..c1fff03a 100644 --- a/ppb/__init__.py +++ b/ppb/__init__.py @@ -47,6 +47,7 @@ from ppb.assets import Rectangle from ppb.assets import Square from ppb.assets import Triangle +from ppb.colors import Color from ppb.engine import GameEngine from ppb.engine import Signal from ppb.scenes import Scene @@ -62,7 +63,7 @@ # Shortcuts 'Scene', 'Sprite', 'RectangleSprite', 'Vector', 'Image', 'Circle', 'Ellipse', 'Square', 'Rectangle', 'Triangle', - 'Font', 'Text', 'Sound', + 'Color', 'Font', 'Text', 'Sound', 'events', 'buttons', 'keycodes', 'flags', 'directions', 'Signal', # Local stuff 'run', 'make_engine', diff --git a/ppb/colors.py b/ppb/colors.py new file mode 100644 index 00000000..66d1710f --- /dev/null +++ b/ppb/colors.py @@ -0,0 +1,13 @@ +class Color(): + """An RGB color.""" + red: int + green: int + blue: int + + def __init__(self, red: int, green: int, blue: int): + self.red = red + self.green = green + self.blue = blue + + def __repr__(self) -> str: + return f'<{type(self).__name__} red={self.red} green={self.green} blue={self.blue}>' \ No newline at end of file From 83dd3a422d8c689d4b0db1e081155691a3b635cb Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 15:26:16 -0400 Subject: [PATCH 02/17] Add color constants and rgb number validation --- ppb/colors.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ppb/colors.py b/ppb/colors.py index 66d1710f..95003519 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -5,9 +5,26 @@ class Color(): blue: int def __init__(self, red: int, green: int, blue: int): + for key, value in {'red': red, 'green': green, 'blue': blue}.items(): + if value < 0: + raise ValueError(f'{key} cannot be less than 0.') + elif value > 255: + raise ValueError(f'{key} cannot be greater than 255.') + self.red = red self.green = green self.blue = blue def __repr__(self) -> str: - return f'<{type(self).__name__} red={self.red} green={self.green} blue={self.blue}>' \ No newline at end of file + return f'<{type(self).__name__} red={self.red} green={self.green} blue={self.blue}>' + + +BLACK = Color(0, 0, 0) +WHITE = Color(255, 255, 255) +GRAY = Color(127, 127, 127) +RED = Color(255, 0, 0) +GREEN = Color(0, 255, 0) +BLUE = Color(0, 0, 255) +CYAN = Color(0, 255, 255) +MAGENTA = Color(255, 0, 255) +YELLOW = Color(255, 255, 0) From 038e82b2c50da681e80ae2b074fc19f73db83808 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 15:46:57 -0400 Subject: [PATCH 03/17] Add dunder methods and refactor assets to use Color --- ppb/__init__.py | 1 + ppb/assets.py | 31 +++++++++++++++---------------- ppb/colors.py | 8 ++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/ppb/__init__.py b/ppb/__init__.py index c1fff03a..cdd33eb0 100644 --- a/ppb/__init__.py +++ b/ppb/__init__.py @@ -32,6 +32,7 @@ * :mod:`buttons` * :mod:`keycodes` * :mod:`flags` +* :mod:`colors` * :mod:`directions` * :class:`Signal` """ diff --git a/ppb/assets.py b/ppb/assets.py index 037e9b5d..a356c37c 100644 --- a/ppb/assets.py +++ b/ppb/assets.py @@ -1,5 +1,6 @@ from ctypes import byref, c_int from typing import NamedTuple, Tuple, Union +from colors import Color, BLACK, MAGENTA import sdl2.ext from sdl2 import ( @@ -31,8 +32,6 @@ "Ellipse" ) -BLACK = 0, 0, 0 -MAGENTA = 255, 71, 182 DEFAULT_SPRITE_SIZE = 64 @@ -41,7 +40,7 @@ class AspectRatio(NamedTuple): height: Union[int, float] -def _create_surface(color, aspect_ratio: AspectRatio = AspectRatio(1, 1)): +def _create_surface(color: Color, aspect_ratio: AspectRatio = AspectRatio(1, 1)): """ Creates a surface for assets and sets the color key. """ @@ -72,8 +71,8 @@ def _create_surface(color, aspect_ratio: AspectRatio = AspectRatio(1, 1)): class Shape(BackgroundMixin, FreeingMixin, AbstractAsset): """Shapes are drawing primitives that are good for rapid prototyping.""" - def __init__(self, red: int, green: int, blue: int, aspect_ratio: aspect_ratio_type = AspectRatio(1, 1)): - self.color = red, green, blue + def __init__(self, color: Color, aspect_ratio: aspect_ratio_type = AspectRatio(1, 1)): + self.color = color self.aspect_ratio = AspectRatio(*aspect_ratio) self._start() @@ -85,7 +84,7 @@ def _background(self): _check_error=lambda rv: not rv ) try: - self._draw_shape(renderer, rgb=self.color) + self._draw_shape(renderer, color=self.color) finally: sdl_call(SDL_DestroyRenderer, renderer) return surface @@ -104,9 +103,9 @@ class Rectangle(Shape): A rectangle image of a single color. """ - def _draw_shape(self, renderer, rgb, **_): + def _draw_shape(self, renderer, color: Color, **_): sdl_call( - SDL_SetRenderDrawColor, renderer, *(int(c) for c in rgb), 255, + SDL_SetRenderDrawColor, renderer, *color, 255, _check_error=lambda rv: rv < 0 ) sdl_call( @@ -120,9 +119,9 @@ class Square(Rectangle): A constructor for :class:`~ppb.Rectangle` that produces a square image. """ - def __init__(self, r, g, b): + def __init__(self, color: Color): # This cuts out the aspect_ratio parameter - super().__init__(r, g, b) + super().__init__(color) class Triangle(Shape): @@ -130,7 +129,7 @@ class Triangle(Shape): A triangle image of a single color. """ - def _draw_shape(self, renderer, rgb, **_): + def _draw_shape(self, renderer, color: Color, **_): w, h = c_int(), c_int() sdl_call(SDL_GetRendererOutputSize, renderer, byref(w), byref(h)) width, height = w.value, h.value @@ -140,7 +139,7 @@ def _draw_shape(self, renderer, rgb, **_): 0, height, int(width / 2), 0, width, height, - *rgb, 255, + *color, 255, _check_error=lambda rv: rv < 0 ) @@ -150,7 +149,7 @@ class Ellipse(Shape): An ellipse image of a single color. """ - def _draw_shape(self, renderer, rgb, **_): + def _draw_shape(self, renderer, color: Color, **_): w, h = c_int(), c_int() sdl_call(SDL_GetRendererOutputSize, renderer, byref(w), byref(h)) half_width, half_height = int(w.value / 2), int(h.value / 2) @@ -159,7 +158,7 @@ def _draw_shape(self, renderer, rgb, **_): filledEllipseRGBA, renderer, half_width, half_height, # Center half_width, half_height, # Radius - *rgb, 255, + *color, 255, _check_error=lambda rv: rv < 0 ) @@ -167,6 +166,6 @@ def _draw_shape(self, renderer, rgb, **_): class Circle(Ellipse): """A convenience constructor for :class:`~ppb.Ellipse` that is a perfect circle.""" - def __init__(self, r, g, b): + def __init__(self, color: Color): # This cuts out the aspect_ratio parameter - super().__init__(r, g, b) + super().__init__(color) diff --git a/ppb/colors.py b/ppb/colors.py index 95003519..f25d7560 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -15,6 +15,14 @@ def __init__(self, red: int, green: int, blue: int): self.green = green self.blue = blue + def __eq__(self, other: object) -> bool: + if not isinstance(other, Color): + return False + return self.red == other.red and self.green == other.green and self.blue == other.blue + + def __iter__(self) -> tuple[int]: + return (self.red, self.green, self.blue) + def __repr__(self) -> str: return f'<{type(self).__name__} red={self.red} green={self.green} blue={self.blue}>' From 49ad451214ba2ea320034c5388b2695884b88b52 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 15:52:11 -0400 Subject: [PATCH 04/17] Changed type of scene background_color --- ppb/scenes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ppb/scenes.py b/ppb/scenes.py index 4b93f0e4..b9564050 100644 --- a/ppb/scenes.py +++ b/ppb/scenes.py @@ -1,3 +1,4 @@ +from colors import Color from typing import Callable from typing import Iterator from typing import Sequence @@ -8,7 +9,7 @@ class Scene(GameObject): # Background color, in RGB, each channel is 0-255 - background_color: Sequence[int] = (0, 0, 100) + background_color: Color camera_class = Camera show_cursor = True From 01040797fa455a1efb3e0b63651976c9618a8325 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 15:52:47 -0400 Subject: [PATCH 05/17] Use new background_color type --- ppb/systems/renderer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ppb/systems/renderer.py b/ppb/systems/renderer.py index 43f534d1..8f2bedbd 100644 --- a/ppb/systems/renderer.py +++ b/ppb/systems/renderer.py @@ -227,9 +227,9 @@ def on_render(self, render_event, signal): sdl_call(SDL_RenderPresent, self.renderer) def render_background(self, scene): - bg = scene.background_color + background_color = scene.background_color sdl_call( - SDL_SetRenderDrawColor, self.renderer, int(bg[0]), int(bg[1]), int(bg[2]), 255, + SDL_SetRenderDrawColor, self.renderer, *background_color, 255, _check_error=lambda rv: rv < 0 ) sdl_call(SDL_RenderClear, self.renderer, _check_error=lambda rv: rv < 0) From 4382568d5becb07ad48e541929c3c6ea4eed0b22 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 16:09:01 -0400 Subject: [PATCH 06/17] Turn Color into dataclass --- ppb/colors.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ppb/colors.py b/ppb/colors.py index f25d7560..1fbc615f 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -1,3 +1,6 @@ +from dataclasses import dataclass + +@dataclass class Color(): """An RGB color.""" red: int @@ -15,16 +18,8 @@ def __init__(self, red: int, green: int, blue: int): self.green = green self.blue = blue - def __eq__(self, other: object) -> bool: - if not isinstance(other, Color): - return False - return self.red == other.red and self.green == other.green and self.blue == other.blue - def __iter__(self) -> tuple[int]: return (self.red, self.green, self.blue) - - def __repr__(self) -> str: - return f'<{type(self).__name__} red={self.red} green={self.green} blue={self.blue}>' BLACK = Color(0, 0, 0) From 4a491acc75c02fc226f8adb8a45365de9f711055 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 16:11:39 -0400 Subject: [PATCH 07/17] Modify tests to use background_color --- docs/reference/scenes.rst | 2 +- examples/rectangular_assets/main.py | 2 +- ppb/assets.py | 2 +- ppb/scenes.py | 2 +- tests/test_engine.py | 6 +++--- tests/test_scenes.py | 9 +++++---- viztests/float_colors.py | 2 +- viztests/primitive_assets.py | 8 ++++---- viztests/show_cursor.py | 6 +++--- viztests/text.py | 2 +- viztests/triangles.py | 18 +++++++++--------- 11 files changed, 30 insertions(+), 29 deletions(-) diff --git a/docs/reference/scenes.rst b/docs/reference/scenes.rst index 67763d66..41b30cd5 100644 --- a/docs/reference/scenes.rst +++ b/docs/reference/scenes.rst @@ -13,7 +13,7 @@ transition at any time. .. autoattribute:: background_color - An RGB triple of the background, eg ``(0, 127, 255)`` + The background color of the scene, e.g. ``ppb.Color(0, 127, 255)`` .. autoattribute:: main_camera diff --git a/examples/rectangular_assets/main.py b/examples/rectangular_assets/main.py index bdbad43c..edfdacb4 100644 --- a/examples/rectangular_assets/main.py +++ b/examples/rectangular_assets/main.py @@ -13,7 +13,7 @@ def setup(scene): - scene.background_color = (0, 0, 0) + scene.background_color = ppb.Color(0, 0, 0) scene.add(ppb.RectangleSprite(width=0.5, height=1, image=tall_rectangle, position=(-2, 2))) scene.add(ppb.RectangleSprite(width=1, height=0.5, image=wide_rectangle, position=(0, 2))) scene.add(ppb.Sprite(size=1, image=square, position=(2, 2))) diff --git a/ppb/assets.py b/ppb/assets.py index a356c37c..2eac37e9 100644 --- a/ppb/assets.py +++ b/ppb/assets.py @@ -1,6 +1,5 @@ from ctypes import byref, c_int from typing import NamedTuple, Tuple, Union -from colors import Color, BLACK, MAGENTA import sdl2.ext from sdl2 import ( @@ -22,6 +21,7 @@ ) from ppb.assetlib import BackgroundMixin, FreeingMixin, AbstractAsset +from ppb.colors import Color, BLACK, MAGENTA from ppb.systems.sdl_utils import sdl_call __all__ = ( diff --git a/ppb/scenes.py b/ppb/scenes.py index b9564050..6c8a18ca 100644 --- a/ppb/scenes.py +++ b/ppb/scenes.py @@ -1,9 +1,9 @@ -from colors import Color from typing import Callable from typing import Iterator from typing import Sequence from ppb.camera import Camera +from ppb.colors import Color from ppb.gomlib import GameObject diff --git a/tests/test_engine.py b/tests/test_engine.py index 8cfbeaed..d9851a7c 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -3,7 +3,7 @@ import pytest -from ppb import GameEngine, Scene, Vector +from ppb import Color, GameEngine, Scene, Vector from ppb import events from ppb.systemslib import System from ppb.systems import Updater @@ -18,7 +18,7 @@ def scenes(): yield Scene yield Scene() - yield Scene(background_color=(0, 0, 0)) + yield Scene(background_color=Color(0, 0, 0)) @pytest.mark.parametrize("scene", scenes()) @@ -31,7 +31,7 @@ def test_engine_initial_scene(scene): def test_game_engine_with_scene_class(): props = { - "background_color": (69, 69, 69), + "background_color": Color(69, 69, 69), "show_cursor": False } with GameEngine(Scene, basic_systems=[Quitter], scene_kwargs=props) as ge: diff --git a/tests/test_scenes.py b/tests/test_scenes.py index ef714927..2e74b071 100644 --- a/tests/test_scenes.py +++ b/tests/test_scenes.py @@ -1,5 +1,6 @@ from pytest import fixture +from ppb.colors import Color from ppb.scenes import Scene from ppb.camera import Camera @@ -22,10 +23,10 @@ def test_main_camera(scene): def test_class_attrs(): class BackgroundScene(Scene): - background_color = (0, 4, 2) + background_color = Color(0, 4, 2) scene = BackgroundScene() - assert scene.background_color == (0, 4, 2) + assert scene.background_color == Color(0, 4, 2) - scene = BackgroundScene(background_color=(2, 4, 0)) - assert scene.background_color == (2, 4, 0) + scene = BackgroundScene(background_color=Color(2, 4, 0)) + assert scene.background_color == Color(2, 4, 0) diff --git a/viztests/float_colors.py b/viztests/float_colors.py index 29bb76a4..c2e44f45 100644 --- a/viztests/float_colors.py +++ b/viztests/float_colors.py @@ -3,7 +3,7 @@ import ppb class MyScene(ppb.Scene): - background_color = (200.5, 125.6, 127.8) + background_color = ppb.Color(200.5, 125.6, 127.8) def setup(scene): scene.add(ppb.Sprite(image=ppb.Square(123.5, 200.8, 156.22))) diff --git a/viztests/primitive_assets.py b/viztests/primitive_assets.py index f369d02d..d556bae0 100644 --- a/viztests/primitive_assets.py +++ b/viztests/primitive_assets.py @@ -18,19 +18,19 @@ def on_update(self, event: ppb.events.Update, signal): class Square(Rotating): - image = ppb.Square(255, 50, 75) + image = ppb.Square(ppb.Color(255, 50, 75)) class Triangle(Rotating): - image = ppb.Triangle(0, 0, 0) + image = ppb.Triangle(ppb.Color(0, 0, 0)) class Circle(Rotating): - image = ppb.Circle(255, 71, 182) + image = ppb.Circle(ppb.Color(255, 71, 182)) def setup(scene): - scene.background_color = (160, 155, 180) + scene.background_color = ppb.Color(160, 155, 180) scene.add(Square(position=ppb.Vector(-2, 0))) scene.add(Triangle(position=ppb.Vector(0, 2))) scene.add(Circle(position=ppb.Vector(2, 0))) diff --git a/viztests/show_cursor.py b/viztests/show_cursor.py index 58d4b12a..972c0780 100644 --- a/viztests/show_cursor.py +++ b/viztests/show_cursor.py @@ -28,7 +28,7 @@ def on_scene_started(self, _, __): image=ppb.Text( " ".join((self.cursor[cursor_state], self._continue)), font=font, - color=(255, 255, 255) + color=ppb.Color(255, 255, 255) ) ) ) @@ -39,7 +39,7 @@ def on_button_pressed(self, event:ppb.events.ButtonPressed, signal): class NoCursorScene(RootScene): - background_color = (100, 100, 100) + background_color = ppb.Color(100, 100, 100) show_cursor = False @@ -48,7 +48,7 @@ class DefaultScene(RootScene): class ExplicitVisibleCursor(RootScene): - background_color = (0, 0, 0) + background_color = ppb.Color(0, 0, 0) show_cursor = True click_event = ppb.events.StartScene(DefaultScene) diff --git a/viztests/text.py b/viztests/text.py index ea1d0f9f..2441fc1e 100644 --- a/viztests/text.py +++ b/viztests/text.py @@ -29,7 +29,7 @@ def on_scene_started(self, event, signal): def on_update(self, event, signal): self.elapsed += event.time_delta - self.background_color = hsv2rgb(self.elapsed / 10, 1.0, 200) + self.background_color = ppb.Color(*hsv2rgb(self.elapsed / 10, 1.0, 200)) ppb.run(starting_scene=TextScene) diff --git a/viztests/triangles.py b/viztests/triangles.py index e8223bfd..5c120e1d 100644 --- a/viztests/triangles.py +++ b/viztests/triangles.py @@ -16,29 +16,29 @@ def setup(scene): - scene.background_color = (0, 0, 0) + scene.background_color = ppb.Color(0, 0, 0) scene.add(ppb.RectangleSprite( width=0.5, height=1, - image=ppb.Rectangle(200, 0, 0, (1, 2)), position=(-2, 2))) + image=ppb.Rectangle(ppb.Color(200, 0, 0), (1, 2)), position=(-2, 2))) scene.add(ppb.RectangleSprite( width=1, height=0.5, - image=ppb.Rectangle(100, 200, 0, (2, 1)), position=(0, 2))) + image=ppb.Rectangle(ppb.Color(100, 200, 0), (2, 1)), position=(0, 2))) scene.add(ppb.Sprite(size=1, image=ppb.Square(200, 200, 100), position=(2, 2))) scene.add(ppb.RectangleSprite( width=0.5, height=1, - image=ppb.Triangle(0, 200, 0, (1, 2)), position=(-2, 0))) + image=ppb.Triangle(ppb.Color(0, 200, 0), (1, 2)), position=(-2, 0))) scene.add(ppb.RectangleSprite( width=1, height=0.5, - image=ppb.Triangle(0, 200, 100, (2, 1)), position=(0, 0))) - scene.add(ppb.Sprite(image=ppb.Triangle(50, 200, 150), position=(2, 0))) + image=ppb.Triangle(ppb.Color(0, 200, 100), (2, 1)), position=(0, 0))) + scene.add(ppb.Sprite(image=ppb.Triangle(ppb.Color(50, 200, 150)), position=(2, 0))) scene.add(ppb.RectangleSprite( width=0.5, height=1, - image=ppb.Ellipse(0, 0, 200, (1, 2)), position=(-2, -2))) + image=ppb.Ellipse(ppb.Color(0, 0, 200), (1, 2)), position=(-2, -2))) scene.add(ppb.RectangleSprite( width=1, height=0.5, - image=ppb.Ellipse(100, 0, 200, (2, 1)), position=(0, -2))) - scene.add(ppb.Sprite(image=ppb.Circle(150, 50, 200), position=(2, -2))) + image=ppb.Ellipse(ppb.Color(100, 0, 200), (2, 1)), position=(0, -2))) + scene.add(ppb.Sprite(image=ppb.Circle(ppb.Color(150, 50, 200)), position=(2, -2))) ppb.run(setup) From fb85d0f99149b4bb04bc8ae56fd0303dde1d94e6 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 16:16:37 -0400 Subject: [PATCH 08/17] Make dataclass frozen --- ppb/colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ppb/colors.py b/ppb/colors.py index 1fbc615f..bbeffc26 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -@dataclass +@dataclass(frozen=True) class Color(): """An RGB color.""" red: int From ffccb05f35989bfe8ae4972c9e99d2888b0b27a8 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 16:20:16 -0400 Subject: [PATCH 09/17] Fix color in a bunch of other example spots --- examples/rectangular_assets/main.py | 18 +++++++++--------- examples/two-phase-updates/three_body.py | 6 +++--- viztests/float_colors.py | 2 +- viztests/rectangles.py | 2 +- viztests/triangles.py | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/rectangular_assets/main.py b/examples/rectangular_assets/main.py index edfdacb4..79f77774 100644 --- a/examples/rectangular_assets/main.py +++ b/examples/rectangular_assets/main.py @@ -1,15 +1,15 @@ import ppb -tall_rectangle = ppb.Rectangle(200, 0, 0, (1, 2)) -wide_rectangle = ppb.Rectangle(100, 200, 0, (2, 1)) -square = ppb.Square(200, 200, 100) -tall_triangle = ppb.Triangle(0, 200, 0, (1, 2)) -wide_triangle = ppb.Triangle(0, 200, 100, (2, 1)) -square_triangle = ppb.Triangle(50, 200, 150) -tall_ellipse = ppb.Ellipse(0, 0, 200, (1, 2)) -wide_ellipse = ppb.Ellipse(100, 0, 200, (2, 1)) -circle = ppb.Circle(150, 50, 200) +tall_rectangle = ppb.Rectangle(ppb.Color(200, 0, 0), (1, 2)) +wide_rectangle = ppb.Rectangle(ppb.Color(100, 200, 0), (2, 1)) +square = ppb.Square(ppb.Color(200, 200, 100)) +tall_triangle = ppb.Triangle(ppb.Color(0, 200, 0), (1, 2)) +wide_triangle = ppb.Triangle(ppb.Color(0, 200, 100), (2, 1)) +square_triangle = ppb.Triangle(ppb.Color(50, 200, 150)) +tall_ellipse = ppb.Ellipse(ppb.Color(0, 0, 200), (1, 2)) +wide_ellipse = ppb.Ellipse(ppb.Color(100, 0, 200), (2, 1)) +circle = ppb.Circle(ppb.Color(150, 50, 200)) def setup(scene): diff --git a/examples/two-phase-updates/three_body.py b/examples/two-phase-updates/three_body.py index 38ac9ac3..b2a8cd27 100644 --- a/examples/two-phase-updates/three_body.py +++ b/examples/two-phase-updates/three_body.py @@ -40,9 +40,9 @@ def on_update(self, event, signal): def setup(scene): - scene.add(Planet(position=(3, 0), velocity=Vector(0, 1), image=ppb.Circle(40, 200, 150))) - scene.add(Planet(position=(-3, 3), velocity=Vector(1, -1), image=ppb.Circle(200, 150, 40))) - scene.add(Planet(position=(-3, -3), velocity=Vector(-1, 0), image=ppb.Circle(150, 40, 200))) + scene.add(Planet(position=(3, 0), velocity=Vector(0, 1), image=ppb.Circle(ppb.Color(40, 200, 150)))) + scene.add(Planet(position=(-3, 3), velocity=Vector(1, -1), image=ppb.Circle(ppb.Color(200, 150, 40)))) + scene.add(Planet(position=(-3, -3), velocity=Vector(-1, 0), image=ppb.Circle(ppb.Color(150, 40, 200)))) if __name__ == "__main__": diff --git a/viztests/float_colors.py b/viztests/float_colors.py index c2e44f45..a807bf6d 100644 --- a/viztests/float_colors.py +++ b/viztests/float_colors.py @@ -6,6 +6,6 @@ class MyScene(ppb.Scene): background_color = ppb.Color(200.5, 125.6, 127.8) def setup(scene): - scene.add(ppb.Sprite(image=ppb.Square(123.5, 200.8, 156.22))) + scene.add(ppb.Sprite(image=ppb.Square(ppb.Color(123.5, 200.8, 156.22)))) ppb.run(setup, starting_scene=MyScene) \ No newline at end of file diff --git a/viztests/rectangles.py b/viztests/rectangles.py index c9dfa543..6514a238 100644 --- a/viztests/rectangles.py +++ b/viztests/rectangles.py @@ -7,7 +7,7 @@ class Square(ppb.sprites.RectangleSprite): width = 1 height = 4 - image = ppb.Square(0, 0, 255) + image = ppb.Square(ppb.Color(0, 0, 255)) class Tall(ppb.sprites.RectangleSprite): diff --git a/viztests/triangles.py b/viztests/triangles.py index 5c120e1d..7bca693f 100644 --- a/viztests/triangles.py +++ b/viztests/triangles.py @@ -24,7 +24,7 @@ def setup(scene): width=1, height=0.5, image=ppb.Rectangle(ppb.Color(100, 200, 0), (2, 1)), position=(0, 2))) scene.add(ppb.Sprite(size=1, - image=ppb.Square(200, 200, 100), position=(2, 2))) + image=ppb.Square(ppb.Color(200, 200, 100)), position=(2, 2))) scene.add(ppb.RectangleSprite( width=0.5, height=1, image=ppb.Triangle(ppb.Color(0, 200, 0), (1, 2)), position=(-2, 0))) From 3766ddacc9987a5d1c00d93bcc298c670edecd1f Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 16:26:08 -0400 Subject: [PATCH 10/17] Fix color validation using post_init --- ppb/colors.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/ppb/colors.py b/ppb/colors.py index bbeffc26..9b250037 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -1,22 +1,18 @@ from dataclasses import dataclass -@dataclass(frozen=True) +@dataclass() class Color(): """An RGB color.""" red: int green: int blue: int - def __init__(self, red: int, green: int, blue: int): - for key, value in {'red': red, 'green': green, 'blue': blue}.items(): + def __post_init__(self): + for key, value in {'red': self.red, 'green': self.green, 'blue': self.blue}.items(): if value < 0: raise ValueError(f'{key} cannot be less than 0.') elif value > 255: raise ValueError(f'{key} cannot be greater than 255.') - - self.red = red - self.green = green - self.blue = blue def __iter__(self) -> tuple[int]: return (self.red, self.green, self.blue) From 0222156dc57523f1b272b7dd14f2cd8aef57e985 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 16:28:59 -0400 Subject: [PATCH 11/17] Remove type annotation --- ppb/colors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ppb/colors.py b/ppb/colors.py index 9b250037..d0e7424e 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -14,7 +14,7 @@ def __post_init__(self): elif value > 255: raise ValueError(f'{key} cannot be greater than 255.') - def __iter__(self) -> tuple[int]: + def __iter__(self): return (self.red, self.green, self.blue) From 107fedb618ee4009945e4f853317da7542759e7d Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 16:46:31 -0400 Subject: [PATCH 12/17] Add from_hsv static method --- ppb/colors.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ppb/colors.py b/ppb/colors.py index d0e7424e..4a0f39fd 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -1,8 +1,9 @@ from dataclasses import dataclass +import colorsys @dataclass() class Color(): - """An RGB color.""" + """An RGB color, with red, green, and blue values ranging from 0-255.""" red: int green: int blue: int @@ -17,6 +18,12 @@ def __post_init__(self): def __iter__(self): return (self.red, self.green, self.blue) + @staticmethod + def from_hsv(hue: float, saturation: float, value: float): + """Convert the given HSV color values to an RGB color.""" + red, green, blue = colorsys.hsv_to_rgb(hue, saturation, value) + return Color(int(red * 256), int(green * 256), int(blue * 256)) + BLACK = Color(0, 0, 0) WHITE = Color(255, 255, 255) From b7aa44b74c238400537bf3288e538965ffed6506 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Mon, 20 May 2024 17:06:39 -0400 Subject: [PATCH 13/17] Refactor text color --- ppb/systems/text.py | 7 ++++--- viztests/text.py | 2 +- viztests/text_shared_font.py | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ppb/systems/text.py b/ppb/systems/text.py index b4741574..f23e3122 100644 --- a/ppb/systems/text.py +++ b/ppb/systems/text.py @@ -21,6 +21,7 @@ from ppb.assetlib import Asset, ChainingMixin, AbstractAsset, FreeingMixin from ppb.systems.sdl_utils import ttf_call +from ppb.colors import Color # From https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html: # [Since 2.5.6] In multi-threaded applications it is easiest to use one @@ -109,11 +110,11 @@ class Text(ChainingMixin, FreeingMixin, AbstractAsset): """ A bit of rendered text. """ - def __init__(self, txt, *, font, color=(0, 0, 0)): + def __init__(self, txt, *, font, color=Color(0, 0, 0)): """ :param txt: The text to display. - :param font: The font to use (a :py:class:`ppb.Font`) - :param color: The color to use. + :param font: The font to use (a :py:class:`ppb.Font`). + :param color: The color to use (a :py:class:`ppb.Color`). """ self.txt = txt self.font = font diff --git a/viztests/text.py b/viztests/text.py index 2441fc1e..2a335e89 100644 --- a/viztests/text.py +++ b/viztests/text.py @@ -22,7 +22,7 @@ def on_scene_started(self, event, signal): image=ppb.Text( "Hello, PPB!", font=ppb.Font(f"resources/ubuntu_font/Ubuntu-{font}.ttf", size=72), - color=hsv2rgb(i / 10, 1.0, 75) + color=ppb.Color(*hsv2rgb(i / 10, 1.0, 75)) ), position=(0, i-4.5), )) diff --git a/viztests/text_shared_font.py b/viztests/text_shared_font.py index 549b53ba..15ef485a 100644 --- a/viztests/text_shared_font.py +++ b/viztests/text_shared_font.py @@ -1,8 +1,8 @@ import ppb font = ppb.Font("resources/ubuntu_font/Ubuntu-R.ttf", size=72) -my_first_text = ppb.Text("My first text", font=font, color=(255, 255, 255)) -my_second_text = ppb.Text("My second text", font=font, color=(255, 255, 255)) +my_first_text = ppb.Text("My first text", font=font, color=ppb.Color(255, 255, 255)) +my_second_text = ppb.Text("My second text", font=font, color=ppb.Color(255, 255, 255)) def setup(scene): From 76831d11d92b67b07781822cf64ca49b9fa2519e Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Tue, 21 May 2024 11:45:12 -0400 Subject: [PATCH 14/17] Add tests for colors --- ppb/colors.py | 7 +++++-- tests/test_colors.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 tests/test_colors.py diff --git a/ppb/colors.py b/ppb/colors.py index 4a0f39fd..d0ed1af4 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -20,9 +20,12 @@ def __iter__(self): @staticmethod def from_hsv(hue: float, saturation: float, value: float): - """Convert the given HSV color values to an RGB color.""" + """ + Create a Color from the given HSV color, + with hue, saturation, and value ranging from 0-1. + """ red, green, blue = colorsys.hsv_to_rgb(hue, saturation, value) - return Color(int(red * 256), int(green * 256), int(blue * 256)) + return Color(round(red * 255), round(green * 255), round(blue * 255)) BLACK = Color(0, 0, 0) diff --git a/tests/test_colors.py b/tests/test_colors.py new file mode 100644 index 00000000..6b57a22f --- /dev/null +++ b/tests/test_colors.py @@ -0,0 +1,33 @@ +import pytest +from ppb import Color +from contextlib import nullcontext as does_not_raise + +@pytest.mark.parametrize( + ['red', 'green', 'blue', 'should_raise'], + [ + [0, 0, 0, False], + [255, 255, 255, False], + [-1, 0, 0, True], + [256, 0, 0, True], + [0, -1, 0, True], + [0, 256, 0, True], + [0, 0, -1, True], + [0, 0, 256, True], + ] +) +def test_colors(red, green, blue, should_raise): + with pytest.raises(ValueError) if should_raise else does_not_raise(): + Color(red, green, blue) + +@pytest.mark.parametrize( + ['hue', 'saturation', 'value', 'red', 'green', 'blue'], + [ + [0, 0, 0, 0, 0, 0], + [0, 0, 1, 255, 255, 255], + [0, 1, 1, 255, 0, 0], + [0.7189, 0.460, 0.545, 95, 75, 139], + ] +) +def test_from_hsv(hue, saturation, value, red, green, blue): + color = Color.from_hsv(hue, saturation, value) + assert color == Color(red, green, blue) \ No newline at end of file From 14ae1c4e9020766e4db103c63248460a82ef77f3 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Tue, 21 May 2024 13:18:16 -0400 Subject: [PATCH 15/17] Refactor colors into Color abstract base class and subclasses --- docs/reference/scenes.rst | 2 +- examples/rectangular_assets/main.py | 20 +++---- examples/two-phase-updates/three_body.py | 6 +- ppb/__init__.py | 4 +- ppb/colors.py | 73 +++++++++++++++++------- ppb/systems/text.py | 6 +- tests/test_colors.py | 36 ++++++++---- tests/test_engine.py | 6 +- tests/test_scenes.py | 10 ++-- viztests/float_colors.py | 4 +- viztests/primitive_assets.py | 8 +-- viztests/rectangles.py | 2 +- viztests/show_cursor.py | 6 +- viztests/text.py | 4 +- viztests/text_shared_font.py | 4 +- viztests/triangles.py | 20 +++---- 16 files changed, 127 insertions(+), 84 deletions(-) diff --git a/docs/reference/scenes.rst b/docs/reference/scenes.rst index 41b30cd5..1cf290bc 100644 --- a/docs/reference/scenes.rst +++ b/docs/reference/scenes.rst @@ -13,7 +13,7 @@ transition at any time. .. autoattribute:: background_color - The background color of the scene, e.g. ``ppb.Color(0, 127, 255)`` + The background color of the scene, e.g. ``ppb.RGBColor(0, 127, 255)`` .. autoattribute:: main_camera diff --git a/examples/rectangular_assets/main.py b/examples/rectangular_assets/main.py index 79f77774..ddbd71c4 100644 --- a/examples/rectangular_assets/main.py +++ b/examples/rectangular_assets/main.py @@ -1,19 +1,19 @@ import ppb -tall_rectangle = ppb.Rectangle(ppb.Color(200, 0, 0), (1, 2)) -wide_rectangle = ppb.Rectangle(ppb.Color(100, 200, 0), (2, 1)) -square = ppb.Square(ppb.Color(200, 200, 100)) -tall_triangle = ppb.Triangle(ppb.Color(0, 200, 0), (1, 2)) -wide_triangle = ppb.Triangle(ppb.Color(0, 200, 100), (2, 1)) -square_triangle = ppb.Triangle(ppb.Color(50, 200, 150)) -tall_ellipse = ppb.Ellipse(ppb.Color(0, 0, 200), (1, 2)) -wide_ellipse = ppb.Ellipse(ppb.Color(100, 0, 200), (2, 1)) -circle = ppb.Circle(ppb.Color(150, 50, 200)) +tall_rectangle = ppb.Rectangle(ppb.RGBColor(200, 0, 0), (1, 2)) +wide_rectangle = ppb.Rectangle(ppb.RGBColor(100, 200, 0), (2, 1)) +square = ppb.Square(ppb.RGBColor(200, 200, 100)) +tall_triangle = ppb.Triangle(ppb.RGBColor(0, 200, 0), (1, 2)) +wide_triangle = ppb.Triangle(ppb.RGBColor(0, 200, 100), (2, 1)) +square_triangle = ppb.Triangle(ppb.RGBColor(50, 200, 150)) +tall_ellipse = ppb.Ellipse(ppb.RGBColor(0, 0, 200), (1, 2)) +wide_ellipse = ppb.Ellipse(ppb.RGBColor(100, 0, 200), (2, 1)) +circle = ppb.Circle(ppb.RGBColor(150, 50, 200)) def setup(scene): - scene.background_color = ppb.Color(0, 0, 0) + scene.background_color = ppb.RGBColor(0, 0, 0) scene.add(ppb.RectangleSprite(width=0.5, height=1, image=tall_rectangle, position=(-2, 2))) scene.add(ppb.RectangleSprite(width=1, height=0.5, image=wide_rectangle, position=(0, 2))) scene.add(ppb.Sprite(size=1, image=square, position=(2, 2))) diff --git a/examples/two-phase-updates/three_body.py b/examples/two-phase-updates/three_body.py index b2a8cd27..0b1f8aba 100644 --- a/examples/two-phase-updates/three_body.py +++ b/examples/two-phase-updates/three_body.py @@ -40,9 +40,9 @@ def on_update(self, event, signal): def setup(scene): - scene.add(Planet(position=(3, 0), velocity=Vector(0, 1), image=ppb.Circle(ppb.Color(40, 200, 150)))) - scene.add(Planet(position=(-3, 3), velocity=Vector(1, -1), image=ppb.Circle(ppb.Color(200, 150, 40)))) - scene.add(Planet(position=(-3, -3), velocity=Vector(-1, 0), image=ppb.Circle(ppb.Color(150, 40, 200)))) + scene.add(Planet(position=(3, 0), velocity=Vector(0, 1), image=ppb.Circle(ppb.RGBColor(40, 200, 150)))) + scene.add(Planet(position=(-3, 3), velocity=Vector(1, -1), image=ppb.Circle(ppb.RGBColor(200, 150, 40)))) + scene.add(Planet(position=(-3, -3), velocity=Vector(-1, 0), image=ppb.Circle(ppb.RGBColor(150, 40, 200)))) if __name__ == "__main__": diff --git a/ppb/__init__.py b/ppb/__init__.py index cdd33eb0..2adf18f2 100644 --- a/ppb/__init__.py +++ b/ppb/__init__.py @@ -48,7 +48,7 @@ from ppb.assets import Rectangle from ppb.assets import Square from ppb.assets import Triangle -from ppb.colors import Color +from ppb.colors import Color, RGBColor, HSVColor from ppb.engine import GameEngine from ppb.engine import Signal from ppb.scenes import Scene @@ -64,7 +64,7 @@ # Shortcuts 'Scene', 'Sprite', 'RectangleSprite', 'Vector', 'Image', 'Circle', 'Ellipse', 'Square', 'Rectangle', 'Triangle', - 'Color', 'Font', 'Text', 'Sound', + 'Color', 'RGBColor', 'HSVColor', 'Font', 'Text', 'Sound', 'events', 'buttons', 'keycodes', 'flags', 'directions', 'Signal', # Local stuff 'run', 'make_engine', diff --git a/ppb/colors.py b/ppb/colors.py index d0ed1af4..4e690f9e 100644 --- a/ppb/colors.py +++ b/ppb/colors.py @@ -1,9 +1,18 @@ from dataclasses import dataclass +from abc import ABC, abstractmethod import colorsys -@dataclass() -class Color(): - """An RGB color, with red, green, and blue values ranging from 0-255.""" +class Color(ABC): + """Abstract base class for a color.""" + @abstractmethod + def to_rgb(self): + """Convert to a tuple of red, green, and blue values.""" + pass + + +@dataclass(frozen=True) +class RGBColor(Color): + """An RGB color, with red, green, and blue values ranging from 0 to 255.""" red: int green: int blue: int @@ -18,22 +27,42 @@ def __post_init__(self): def __iter__(self): return (self.red, self.green, self.blue) - @staticmethod - def from_hsv(hue: float, saturation: float, value: float): - """ - Create a Color from the given HSV color, - with hue, saturation, and value ranging from 0-1. - """ - red, green, blue = colorsys.hsv_to_rgb(hue, saturation, value) - return Color(round(red * 255), round(green * 255), round(blue * 255)) - - -BLACK = Color(0, 0, 0) -WHITE = Color(255, 255, 255) -GRAY = Color(127, 127, 127) -RED = Color(255, 0, 0) -GREEN = Color(0, 255, 0) -BLUE = Color(0, 0, 255) -CYAN = Color(0, 255, 255) -MAGENTA = Color(255, 0, 255) -YELLOW = Color(255, 255, 0) + def to_rgb(self): + return (self.red, self.green, self.blue) + + +@dataclass(frozen=True) +class HSVColor(Color): + """ + An HSV color, with hue ranging from 0 to 360, + saturation ranging from 0 to 100, and value ranging from 0 to 100. + """ + hue: float + saturation: float + value: float + + def __post_init__(self): + for key, (value, max_value) in { + 'hue': (self.hue, 360), + 'saturation': (self.saturation, 100), + 'value': (self.value, 100), + }.items(): + if value < 0: + raise ValueError(f'{key} cannot be less than 0.') + elif value > max_value: + raise ValueError(f'{key} cannot be greater than {max_value}.') + + def to_rgb(self): + red, green, blue = colorsys.hsv_to_rgb(self.hue / 360, self.saturation / 100, self.value / 100) + return (round(red * 255), round(green * 255), round(blue * 255)) + + +BLACK = RGBColor(0, 0, 0) +WHITE = RGBColor(255, 255, 255) +GRAY = RGBColor(127, 127, 127) +RED = RGBColor(255, 0, 0) +GREEN = RGBColor(0, 255, 0) +BLUE = RGBColor(0, 0, 255) +CYAN = RGBColor(0, 255, 255) +MAGENTA = RGBColor(255, 0, 255) +YELLOW = RGBColor(255, 255, 0) diff --git a/ppb/systems/text.py b/ppb/systems/text.py index f23e3122..fc25d8d2 100644 --- a/ppb/systems/text.py +++ b/ppb/systems/text.py @@ -21,7 +21,7 @@ from ppb.assetlib import Asset, ChainingMixin, AbstractAsset, FreeingMixin from ppb.systems.sdl_utils import ttf_call -from ppb.colors import Color +from ppb.colors import Color, RGBColor # From https://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html: # [Since 2.5.6] In multi-threaded applications it is easiest to use one @@ -110,7 +110,7 @@ class Text(ChainingMixin, FreeingMixin, AbstractAsset): """ A bit of rendered text. """ - def __init__(self, txt, *, font, color=Color(0, 0, 0)): + def __init__(self, txt, *, font, color=RGBColor(0, 0, 0)): """ :param txt: The text to display. :param font: The font to use (a :py:class:`ppb.Font`). @@ -129,7 +129,7 @@ def _background(self): with _freetype_lock: return ttf_call( TTF_RenderUTF8_Blended, self.font.load(), self.txt.encode('utf-8'), - SDL_Color(*self.color), + SDL_Color(self.color.to_rgb()), _check_error=lambda rv: not rv ) diff --git a/tests/test_colors.py b/tests/test_colors.py index 6b57a22f..0a6979a3 100644 --- a/tests/test_colors.py +++ b/tests/test_colors.py @@ -1,5 +1,5 @@ import pytest -from ppb import Color +from ppb import Color, RGBColor, HSVColor from contextlib import nullcontext as does_not_raise @pytest.mark.parametrize( @@ -15,19 +15,33 @@ [0, 0, 256, True], ] ) -def test_colors(red, green, blue, should_raise): +def test_rgb_color_validation(red, green, blue, should_raise): with pytest.raises(ValueError) if should_raise else does_not_raise(): - Color(red, green, blue) + RGBColor(red, green, blue) @pytest.mark.parametrize( - ['hue', 'saturation', 'value', 'red', 'green', 'blue'], + ['hue', 'saturation', 'value', 'should_raise'], [ - [0, 0, 0, 0, 0, 0], - [0, 0, 1, 255, 255, 255], - [0, 1, 1, 255, 0, 0], - [0.7189, 0.460, 0.545, 95, 75, 139], + [0, 0, 0, False], + [360, 100, 100, False], + [-1, 0, 0, True], + [361, 0, 0, True], + [0, -1, 0, True], + [0, 101, 0, True], + [0, 0, -1, True], + [0, 0, 101, True], + ] +) +def test_hsv_color_validation(hue, saturation, value, should_raise): + with pytest.raises(ValueError) if should_raise else does_not_raise(): + HSVColor(hue, saturation, value) + +@pytest.mark.parametrize( + ['color', 'red', 'green', 'blue'], + [ + [RGBColor(50, 40, 30), 50, 40, 30], + [HSVColor(259, 46, 54.5), 95, 75, 139], ] ) -def test_from_hsv(hue, saturation, value, red, green, blue): - color = Color.from_hsv(hue, saturation, value) - assert color == Color(red, green, blue) \ No newline at end of file +def test_to_rgb(color, red, green, blue): + assert color.to_rgb() == (red, green, blue) \ No newline at end of file diff --git a/tests/test_engine.py b/tests/test_engine.py index d9851a7c..380bc1c2 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -3,7 +3,7 @@ import pytest -from ppb import Color, GameEngine, Scene, Vector +from ppb import RGBColor, GameEngine, Scene, Vector from ppb import events from ppb.systemslib import System from ppb.systems import Updater @@ -18,7 +18,7 @@ def scenes(): yield Scene yield Scene() - yield Scene(background_color=Color(0, 0, 0)) + yield Scene(background_color=RGBColor(0, 0, 0)) @pytest.mark.parametrize("scene", scenes()) @@ -31,7 +31,7 @@ def test_engine_initial_scene(scene): def test_game_engine_with_scene_class(): props = { - "background_color": Color(69, 69, 69), + "background_color": RGBColor(69, 69, 69), "show_cursor": False } with GameEngine(Scene, basic_systems=[Quitter], scene_kwargs=props) as ge: diff --git a/tests/test_scenes.py b/tests/test_scenes.py index 2e74b071..f956921a 100644 --- a/tests/test_scenes.py +++ b/tests/test_scenes.py @@ -1,6 +1,6 @@ from pytest import fixture -from ppb.colors import Color +from ppb.colors import RGBColor from ppb.scenes import Scene from ppb.camera import Camera @@ -23,10 +23,10 @@ def test_main_camera(scene): def test_class_attrs(): class BackgroundScene(Scene): - background_color = Color(0, 4, 2) + background_color = RGBColor(0, 4, 2) scene = BackgroundScene() - assert scene.background_color == Color(0, 4, 2) + assert scene.background_color == RGBColor(0, 4, 2) - scene = BackgroundScene(background_color=Color(2, 4, 0)) - assert scene.background_color == Color(2, 4, 0) + scene = BackgroundScene(background_color=RGBColor(2, 4, 0)) + assert scene.background_color == RGBColor(2, 4, 0) diff --git a/viztests/float_colors.py b/viztests/float_colors.py index a807bf6d..c2220faa 100644 --- a/viztests/float_colors.py +++ b/viztests/float_colors.py @@ -3,9 +3,9 @@ import ppb class MyScene(ppb.Scene): - background_color = ppb.Color(200.5, 125.6, 127.8) + background_color = ppb.RGBColor(200.5, 125.6, 127.8) def setup(scene): - scene.add(ppb.Sprite(image=ppb.Square(ppb.Color(123.5, 200.8, 156.22)))) + scene.add(ppb.Sprite(image=ppb.Square(ppb.RGBColor(123.5, 200.8, 156.22)))) ppb.run(setup, starting_scene=MyScene) \ No newline at end of file diff --git a/viztests/primitive_assets.py b/viztests/primitive_assets.py index d556bae0..b8e60faf 100644 --- a/viztests/primitive_assets.py +++ b/viztests/primitive_assets.py @@ -18,19 +18,19 @@ def on_update(self, event: ppb.events.Update, signal): class Square(Rotating): - image = ppb.Square(ppb.Color(255, 50, 75)) + image = ppb.Square(ppb.RGBColor(255, 50, 75)) class Triangle(Rotating): - image = ppb.Triangle(ppb.Color(0, 0, 0)) + image = ppb.Triangle(ppb.RGBColor(0, 0, 0)) class Circle(Rotating): - image = ppb.Circle(ppb.Color(255, 71, 182)) + image = ppb.Circle(ppb.RGBColor(255, 71, 182)) def setup(scene): - scene.background_color = ppb.Color(160, 155, 180) + scene.background_color = ppb.RGBColor(160, 155, 180) scene.add(Square(position=ppb.Vector(-2, 0))) scene.add(Triangle(position=ppb.Vector(0, 2))) scene.add(Circle(position=ppb.Vector(2, 0))) diff --git a/viztests/rectangles.py b/viztests/rectangles.py index 6514a238..4d3bcc10 100644 --- a/viztests/rectangles.py +++ b/viztests/rectangles.py @@ -7,7 +7,7 @@ class Square(ppb.sprites.RectangleSprite): width = 1 height = 4 - image = ppb.Square(ppb.Color(0, 0, 255)) + image = ppb.Square(ppb.RGBColor(0, 0, 255)) class Tall(ppb.sprites.RectangleSprite): diff --git a/viztests/show_cursor.py b/viztests/show_cursor.py index 972c0780..35b6dd98 100644 --- a/viztests/show_cursor.py +++ b/viztests/show_cursor.py @@ -28,7 +28,7 @@ def on_scene_started(self, _, __): image=ppb.Text( " ".join((self.cursor[cursor_state], self._continue)), font=font, - color=ppb.Color(255, 255, 255) + color=ppb.RGBColor(255, 255, 255) ) ) ) @@ -39,7 +39,7 @@ def on_button_pressed(self, event:ppb.events.ButtonPressed, signal): class NoCursorScene(RootScene): - background_color = ppb.Color(100, 100, 100) + background_color = ppb.RGBColor(100, 100, 100) show_cursor = False @@ -48,7 +48,7 @@ class DefaultScene(RootScene): class ExplicitVisibleCursor(RootScene): - background_color = ppb.Color(0, 0, 0) + background_color = ppb.RGBColor(0, 0, 0) show_cursor = True click_event = ppb.events.StartScene(DefaultScene) diff --git a/viztests/text.py b/viztests/text.py index 2a335e89..0a97b704 100644 --- a/viztests/text.py +++ b/viztests/text.py @@ -22,14 +22,14 @@ def on_scene_started(self, event, signal): image=ppb.Text( "Hello, PPB!", font=ppb.Font(f"resources/ubuntu_font/Ubuntu-{font}.ttf", size=72), - color=ppb.Color(*hsv2rgb(i / 10, 1.0, 75)) + color=ppb.RGBColor(*hsv2rgb(i / 10, 1.0, 75)) ), position=(0, i-4.5), )) def on_update(self, event, signal): self.elapsed += event.time_delta - self.background_color = ppb.Color(*hsv2rgb(self.elapsed / 10, 1.0, 200)) + self.background_color = ppb.RGBColor(*hsv2rgb(self.elapsed / 10, 1.0, 200)) ppb.run(starting_scene=TextScene) diff --git a/viztests/text_shared_font.py b/viztests/text_shared_font.py index 15ef485a..99f2faa7 100644 --- a/viztests/text_shared_font.py +++ b/viztests/text_shared_font.py @@ -1,8 +1,8 @@ import ppb font = ppb.Font("resources/ubuntu_font/Ubuntu-R.ttf", size=72) -my_first_text = ppb.Text("My first text", font=font, color=ppb.Color(255, 255, 255)) -my_second_text = ppb.Text("My second text", font=font, color=ppb.Color(255, 255, 255)) +my_first_text = ppb.Text("My first text", font=font, color=ppb.RGBColor(255, 255, 255)) +my_second_text = ppb.Text("My second text", font=font, color=ppb.RGBColor(255, 255, 255)) def setup(scene): diff --git a/viztests/triangles.py b/viztests/triangles.py index 7bca693f..b23b4779 100644 --- a/viztests/triangles.py +++ b/viztests/triangles.py @@ -16,29 +16,29 @@ def setup(scene): - scene.background_color = ppb.Color(0, 0, 0) + scene.background_color = ppb.RGBColor(0, 0, 0) scene.add(ppb.RectangleSprite( width=0.5, height=1, - image=ppb.Rectangle(ppb.Color(200, 0, 0), (1, 2)), position=(-2, 2))) + image=ppb.Rectangle(ppb.RGBColor(200, 0, 0), (1, 2)), position=(-2, 2))) scene.add(ppb.RectangleSprite( width=1, height=0.5, - image=ppb.Rectangle(ppb.Color(100, 200, 0), (2, 1)), position=(0, 2))) + image=ppb.Rectangle(ppb.RGBColor(100, 200, 0), (2, 1)), position=(0, 2))) scene.add(ppb.Sprite(size=1, - image=ppb.Square(ppb.Color(200, 200, 100)), position=(2, 2))) + image=ppb.Square(ppb.RGBColor(200, 200, 100)), position=(2, 2))) scene.add(ppb.RectangleSprite( width=0.5, height=1, - image=ppb.Triangle(ppb.Color(0, 200, 0), (1, 2)), position=(-2, 0))) + image=ppb.Triangle(ppb.RGBColor(0, 200, 0), (1, 2)), position=(-2, 0))) scene.add(ppb.RectangleSprite( width=1, height=0.5, - image=ppb.Triangle(ppb.Color(0, 200, 100), (2, 1)), position=(0, 0))) - scene.add(ppb.Sprite(image=ppb.Triangle(ppb.Color(50, 200, 150)), position=(2, 0))) + image=ppb.Triangle(ppb.RGBColor(0, 200, 100), (2, 1)), position=(0, 0))) + scene.add(ppb.Sprite(image=ppb.Triangle(ppb.RGBColor(50, 200, 150)), position=(2, 0))) scene.add(ppb.RectangleSprite( width=0.5, height=1, - image=ppb.Ellipse(ppb.Color(0, 0, 200), (1, 2)), position=(-2, -2))) + image=ppb.Ellipse(ppb.RGBColor(0, 0, 200), (1, 2)), position=(-2, -2))) scene.add(ppb.RectangleSprite( width=1, height=0.5, - image=ppb.Ellipse(ppb.Color(100, 0, 200), (2, 1)), position=(0, -2))) - scene.add(ppb.Sprite(image=ppb.Circle(ppb.Color(150, 50, 200)), position=(2, -2))) + image=ppb.Ellipse(ppb.RGBColor(100, 0, 200), (2, 1)), position=(0, -2))) + scene.add(ppb.Sprite(image=ppb.Circle(ppb.RGBColor(150, 50, 200)), position=(2, -2))) ppb.run(setup) From 6da63f3c96ec016427e86fef61a8e40ee157b246 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Tue, 21 May 2024 13:23:32 -0400 Subject: [PATCH 16/17] Use to_rgb() in the backend --- ppb/assets.py | 2 +- ppb/systems/renderer.py | 2 +- ppb/systems/text.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ppb/assets.py b/ppb/assets.py index 2eac37e9..98ffc32b 100644 --- a/ppb/assets.py +++ b/ppb/assets.py @@ -57,7 +57,7 @@ def _create_surface(color: Color, aspect_ratio: AspectRatio = AspectRatio(1, 1)) _check_error=lambda rv: not rv ) color_key = BLACK if color != BLACK else MAGENTA - color = sdl2.ext.Color(*color_key) + color = sdl2.ext.Color(color_key.to_rgb()) sdl_call( SDL_SetColorKey, surface, True, sdl2.ext.prepare_color(color, surface.contents), _check_error=lambda rv: rv < 0 diff --git a/ppb/systems/renderer.py b/ppb/systems/renderer.py index 8f2bedbd..3c78ae89 100644 --- a/ppb/systems/renderer.py +++ b/ppb/systems/renderer.py @@ -229,7 +229,7 @@ def on_render(self, render_event, signal): def render_background(self, scene): background_color = scene.background_color sdl_call( - SDL_SetRenderDrawColor, self.renderer, *background_color, 255, + SDL_SetRenderDrawColor, self.renderer, *background_color.to_rgb(), 255, _check_error=lambda rv: rv < 0 ) sdl_call(SDL_RenderClear, self.renderer, _check_error=lambda rv: rv < 0) diff --git a/ppb/systems/text.py b/ppb/systems/text.py index fc25d8d2..8e530af0 100644 --- a/ppb/systems/text.py +++ b/ppb/systems/text.py @@ -129,7 +129,7 @@ def _background(self): with _freetype_lock: return ttf_call( TTF_RenderUTF8_Blended, self.font.load(), self.txt.encode('utf-8'), - SDL_Color(self.color.to_rgb()), + SDL_Color(*self.color.to_rgb()), _check_error=lambda rv: not rv ) From a5a77291360f7a9c9efea292729f474367eaf882 Mon Sep 17 00:00:00 2001 From: Adam Aaronson Date: Tue, 21 May 2024 13:26:39 -0400 Subject: [PATCH 17/17] Fix color unpacking --- ppb/assets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ppb/assets.py b/ppb/assets.py index 98ffc32b..12603346 100644 --- a/ppb/assets.py +++ b/ppb/assets.py @@ -57,7 +57,7 @@ def _create_surface(color: Color, aspect_ratio: AspectRatio = AspectRatio(1, 1)) _check_error=lambda rv: not rv ) color_key = BLACK if color != BLACK else MAGENTA - color = sdl2.ext.Color(color_key.to_rgb()) + color = sdl2.ext.Color(*color_key.to_rgb()) sdl_call( SDL_SetColorKey, surface, True, sdl2.ext.prepare_color(color, surface.contents), _check_error=lambda rv: rv < 0 @@ -105,7 +105,7 @@ class Rectangle(Shape): def _draw_shape(self, renderer, color: Color, **_): sdl_call( - SDL_SetRenderDrawColor, renderer, *color, 255, + SDL_SetRenderDrawColor, renderer, *color.to_rgb(), 255, _check_error=lambda rv: rv < 0 ) sdl_call( @@ -139,7 +139,7 @@ def _draw_shape(self, renderer, color: Color, **_): 0, height, int(width / 2), 0, width, height, - *color, 255, + *color.to_rgb(), 255, _check_error=lambda rv: rv < 0 ) @@ -158,7 +158,7 @@ def _draw_shape(self, renderer, color: Color, **_): filledEllipseRGBA, renderer, half_width, half_height, # Center half_width, half_height, # Radius - *color, 255, + *color.to_rgb(), 255, _check_error=lambda rv: rv < 0 )