Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add special_flags to Group.draw() #3321

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions buildconfig/stubs/pygame/sprite.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class AbstractGroup(Generic[_TSprite]):
self, *sprites: Union[_TSprite, AbstractGroup[_TSprite], Iterable[_TSprite]]
) -> bool: ...
def update(self, *args: Any, **kwargs: Any) -> None: ...
def draw(self, surface: Surface) -> list[Union[FRect, Rect]]: ...
def draw(self, surface: Surface, bgd: Optional[Surface] = None, special_flags: int = 0) -> list[Union[FRect, Rect]]: ...
def clear(
self,
surface: Surface,
Expand Down Expand Up @@ -225,7 +225,7 @@ class LayeredUpdates(AbstractGroup[_TSprite]):
class LayeredDirty(LayeredUpdates[_TDirtySprite]):
def __init__(self, *sprites: _TDirtySprite, **kwargs: Any) -> None: ...
def draw(
self, surface: Surface, bgd: Optional[Surface] = None
self, surface: Surface, bgd: Optional[Surface] = None, special_flags: Optional[int] = None
) -> list[Union[FRect, Rect]]: ...
# clear breaks Liskov substitution principle in code
def clear(self, surface: Surface, bgd: Surface) -> None: ... # type: ignore[override]
Expand Down
27 changes: 20 additions & 7 deletions docs/reST/ref/sprite.rst
Original file line number Diff line number Diff line change
Expand Up @@ -291,14 +291,18 @@ Sprites are not thread safe. So lock them yourself if using threads.
.. method:: draw

| :sl:`blit the Sprite images`
| :sg:`draw(Surface) -> list[Rect]`
| :sg:`draw(Surface, bgd=None, special_flags=0) -> list[Rect]`

Draws the contained Sprites to the Surface argument. This uses the
``Sprite.image`` attribute for the source surface, and ``Sprite.rect``
for the position.
for the position. ``special_flags`` is passed to ``Surface.blit()``.
``bgd`` is unused in this method but ``LayeredDirty.draw()`` uses
it.

The Group keeps sprites in the order they were added, they will be drawn in this order.

.. versionchanged:: 2.5.4 Added the ``bgd`` and ``special_flags`` arguments

.. ## Group.draw ##

.. method:: clear
Expand Down Expand Up @@ -347,18 +351,22 @@ Sprites are not thread safe. So lock them yourself if using threads.
.. method:: draw

| :sl:`blit the Sprite images and track changed areas`
| :sg:`draw(surface) -> Rect_list`
| :sg:`draw(surface, bgd=None, special_flags=0) -> Rect_list`

Draws all the Sprites to the surface, the same as ``Group.draw()``. This
method also returns a list of Rectangular areas on the screen that have
been changed. The returned changes include areas of the screen that have
been affected by previous ``Group.clear()`` calls.
been affected by previous ``Group.clear()`` calls. ``special_flags`` is
passed to ``Surface.blit()``. ``bgd`` is unused in this method but
``LayeredDirty.draw()`` uses it.

The returned Rect list should be passed to ``pygame.display.update()``.
This will help performance on software driven display modes. This type of
updating is usually only helpful on destinations with non-animating
backgrounds.

.. versionchanged:: 2.5.4 Added the ``bgd`` and ``special_flags`` arguments

.. ## RenderUpdates.draw ##

.. ## pygame.sprite.RenderUpdates ##
Expand Down Expand Up @@ -403,7 +411,9 @@ Sprites are not thread safe. So lock them yourself if using threads.
.. method:: draw

| :sl:`draw all sprites in the right order onto the passed surface.`
| :sg:`draw(surface) -> Rect_list`
| :sg:`draw(surface, bgd=None, special_flags=0) -> Rect_list`

.. versionchanged:: 2.5.4 Added the ``bgd`` and ``special_flags`` arguments

.. ## LayeredUpdates.draw ##

Expand Down Expand Up @@ -551,10 +561,13 @@ Sprites are not thread safe. So lock them yourself if using threads.
.. method:: draw

| :sl:`draw all sprites in the right order onto the passed surface.`
| :sg:`draw(surface, bgd=None) -> Rect_list`
| :sg:`draw(surface, bgd=None, special_flags=None) -> Rect_list`

You can pass the background too. If a background is already set, then the
bgd argument has no effect.
bgd argument has no effect. ``special_flags`` is passed to
``Surface.blit()``.

.. versionchanged:: 2.5.4 Added the ``special_flags`` argument

.. ## LayeredDirty.draw ##

Expand Down
8 changes: 4 additions & 4 deletions src_c/doc/sprite_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
#define DOC_SPRITE_GROUP_REMOVE "remove(*sprites) -> None\nremove Sprites from the Group"
#define DOC_SPRITE_GROUP_HAS "has(*sprites) -> bool\ntest if a Group contains Sprites"
#define DOC_SPRITE_GROUP_UPDATE "update(*args, **kwargs) -> None\ncall the update method on contained Sprites"
#define DOC_SPRITE_GROUP_DRAW "draw(Surface) -> list[Rect]\nblit the Sprite images"
#define DOC_SPRITE_GROUP_DRAW "draw(Surface, bgd=None, special_flags=0) -> list[Rect]\nblit the Sprite images"
#define DOC_SPRITE_GROUP_CLEAR "clear(Surface_dest, background) -> None\ndraw a background over the Sprites"
#define DOC_SPRITE_GROUP_EMPTY "empty() -> None\nremove all Sprites"
#define DOC_SPRITE_RENDERUPDATES "RenderUpdates(*sprites) -> RenderUpdates\nGroup sub-class that tracks dirty updates."
#define DOC_SPRITE_RENDERUPDATES_DRAW "draw(surface) -> Rect_list\nblit the Sprite images and track changed areas"
#define DOC_SPRITE_RENDERUPDATES_DRAW "draw(surface, bgd=None, special_flags=0) -> Rect_list\nblit the Sprite images and track changed areas"
#define DOC_SPRITE_LAYEREDUPDATES "LayeredUpdates(*sprites, **kwargs) -> LayeredUpdates\nLayeredUpdates is a sprite group that handles layers and draws like RenderUpdates."
#define DOC_SPRITE_LAYEREDUPDATES_ADD "add(*sprites, **kwargs) -> None\nadd a sprite or sequence of sprites to a group"
#define DOC_SPRITE_LAYEREDUPDATES_SPRITES "sprites() -> sprites\nreturns an ordered list of sprites (first back, last top)."
#define DOC_SPRITE_LAYEREDUPDATES_DRAW "draw(surface) -> Rect_list\ndraw all sprites in the right order onto the passed surface."
#define DOC_SPRITE_LAYEREDUPDATES_DRAW "draw(surface, bgd=None, special_flags=0) -> Rect_list\ndraw all sprites in the right order onto the passed surface."
#define DOC_SPRITE_LAYEREDUPDATES_GETSPRITESAT "get_sprites_at(pos) -> colliding_sprites\nreturns a list with all sprites at that position."
#define DOC_SPRITE_LAYEREDUPDATES_GETSPRITE "get_sprite(idx) -> sprite\nreturns the sprite at the index idx from the groups sprites"
#define DOC_SPRITE_LAYEREDUPDATES_REMOVESPRITESOFLAYER "remove_sprites_of_layer(layer_nr) -> sprites\nremoves all sprites from a layer and returns them as a list."
Expand All @@ -38,7 +38,7 @@
#define DOC_SPRITE_LAYEREDUPDATES_GETSPRITESFROMLAYER "get_sprites_from_layer(layer) -> sprites\nreturns all sprites from a layer, ordered by how they where added"
#define DOC_SPRITE_LAYEREDUPDATES_SWITCHLAYER "switch_layer(layer1_nr, layer2_nr) -> None\nswitches the sprites from layer1 to layer2"
#define DOC_SPRITE_LAYEREDDIRTY "LayeredDirty(*sprites, **kwargs) -> LayeredDirty\nLayeredDirty group is for DirtySprite objects. Subclasses LayeredUpdates."
#define DOC_SPRITE_LAYEREDDIRTY_DRAW "draw(surface, bgd=None) -> Rect_list\ndraw all sprites in the right order onto the passed surface."
#define DOC_SPRITE_LAYEREDDIRTY_DRAW "draw(surface, bgd=None, special_flags=None) -> Rect_list\ndraw all sprites in the right order onto the passed surface."
#define DOC_SPRITE_LAYEREDDIRTY_CLEAR "clear(surface, bgd) -> None\nused to set background"
#define DOC_SPRITE_LAYEREDDIRTY_REPAINTRECT "repaint_rect(screen_rect) -> None\nrepaints the given area"
#define DOC_SPRITE_LAYEREDDIRTY_SETCLIP "set_clip(screen_rect=None) -> None\nclip the area where to draw. Just pass None (default) to reset the clip"
Expand Down
57 changes: 39 additions & 18 deletions src_py/sprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,22 +557,29 @@ def update(self, *args, **kwargs):
for sprite in self.sprites():
sprite.update(*args, **kwargs)

def draw(self, surface):
def draw(self, surface, bgd=None, special_flags=0): # noqa pylint: disable=unused-argument; bgd arg used in LayeredDirty
"""draw all sprites onto the surface

Group.draw(surface): return Rect_list
Group.draw(surface, bgd=None, special_flags=0): return Rect_list

Draws all of the member sprites onto the given surface.

"""
sprites = self.sprites()
if hasattr(surface, "blits"):
self.spritedict.update(
zip(sprites, surface.blits((spr.image, spr.rect) for spr in sprites))
zip(
sprites,
surface.blits(
(spr.image, spr.rect, None, special_flags) for spr in sprites
),
)
)
else:
for spr in sprites:
self.spritedict[spr] = surface.blit(spr.image, spr.rect)
self.spritedict[spr] = surface.blit(
spr.image, spr.rect, None, special_flags
)
self.lostsprites = []
dirty = self.lostsprites

Expand Down Expand Up @@ -687,14 +694,14 @@ class RenderUpdates(Group):

"""

def draw(self, surface):
def draw(self, surface, bgd=None, special_flags=0):
surface_blit = surface.blit
dirty = self.lostsprites
self.lostsprites = []
dirty_append = dirty.append
for sprite in self.sprites():
old_rect = self.spritedict[sprite]
new_rect = surface_blit(sprite.image, sprite.rect)
new_rect = surface_blit(sprite.image, sprite.rect, None, special_flags)
if old_rect:
if new_rect.colliderect(old_rect):
dirty_append(new_rect.union(old_rect))
Expand Down Expand Up @@ -856,10 +863,10 @@ def sprites(self):
"""
return self._spritelist.copy()

def draw(self, surface):
def draw(self, surface, bgd=None, special_flags=0):
"""draw all sprites in the right order onto the passed surface

LayeredUpdates.draw(surface): return Rect_list
LayeredUpdates.draw(surface, bgd=None, special_flags=0): return Rect_list

"""
spritedict = self.spritedict
Expand All @@ -870,7 +877,7 @@ def draw(self, surface):
init_rect = self._init_rect
for spr in self.sprites():
rec = spritedict[spr]
newrect = surface_blit(spr.image, spr.rect)
newrect = surface_blit(spr.image, spr.rect, None, special_flags)
if rec is init_rect:
dirty_append(newrect)
else:
Expand Down Expand Up @@ -1129,13 +1136,16 @@ def add_internal(self, sprite, layer=None):

LayeredUpdates.add_internal(self, sprite, layer)

def draw(self, surface, bgd=None): # noqa pylint: disable=arguments-differ; unable to change public interface
def draw(self, surface, bgd=None, special_flags=None):
"""draw all sprites in the right order onto the given surface

LayeredDirty.draw(surface, bgd=None): return Rect_list
LayeredDirty.draw(surface, bgd=None, special_flags=0): return Rect_list

You can pass the background too. If a self.bgd is already set to some
value that is not None, then the bgd argument has no effect.
Passing a value to special_flags will pass that value as the
special_flags argument to all Surface.blit calls, overriding
the sprite.blendmode attribute.

"""
# functions and classes assigned locally to speed up loops
Expand Down Expand Up @@ -1175,21 +1185,29 @@ def draw(self, surface, bgd=None): # noqa pylint: disable=arguments-differ; una

# clear using background
if local_bgd is not None:
flags = 0 if special_flags is None else special_flags
for rec in local_update:
surf_blit_func(local_bgd, rec, rec)
surf_blit_func(local_bgd, rec, rec, flags)

# 2. draw
self._draw_dirty_internal(
local_old_rect, rect_type, local_sprites, surf_blit_func, local_update
local_old_rect,
rect_type,
local_sprites,
surf_blit_func,
local_update,
special_flags,
)
local_ret = list(local_update)
else: # flip, full screen mode
if local_bgd is not None:
surf_blit_func(local_bgd, (0, 0))
flags = 0 if special_flags is None else special_flags
surf_blit_func(local_bgd, (0, 0), None, flags)
for spr in local_sprites:
if spr.visible:
flags = spr.blendmode if special_flags is None else special_flags
local_old_rect[spr] = surf_blit_func(
spr.image, spr.rect, spr.source_rect, spr.blendmode
spr.image, spr.rect, spr.source_rect, flags
)
# return only the part of the screen changed
local_ret = [rect_type(latest_clip)]
Expand All @@ -1211,8 +1229,11 @@ def draw(self, surface, bgd=None): # noqa pylint: disable=arguments-differ; una
return local_ret

@staticmethod
def _draw_dirty_internal(_old_rect, _rect, _sprites, _surf_blit, _update):
def _draw_dirty_internal(
_old_rect, _rect, _sprites, _surf_blit, _update, _special_flags
):
for spr in _sprites:
flags = spr.blendmode if _special_flags is None else _special_flags
if spr.dirty < 1 and spr.visible:
# sprite not dirty; blit only the intersecting part
if spr.source_rect is not None:
Expand Down Expand Up @@ -1240,12 +1261,12 @@ def _draw_dirty_internal(_old_rect, _rect, _sprites, _surf_blit, _update):
clip[2],
clip[3],
),
spr.blendmode,
flags,
)
else: # dirty sprite
if spr.visible:
_old_rect[spr] = _surf_blit(
spr.image, spr.rect, spr.source_rect, spr.blendmode
spr.image, spr.rect, spr.source_rect, flags
)
if spr.dirty == 1:
spr.dirty = 0
Expand Down