Skip to content

Improve NinePatchTexture doc #1682

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

Merged
merged 8 commits into from
Apr 6, 2023
127 changes: 78 additions & 49 deletions arcade/gui/nine_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,63 @@

class NinePatchTexture:
"""
A 9-patch texture is a texture that can be stretched in specific ways to keep
the edges a specific width/height. This is useful for GUI elements that need
to be stretched but have a specific border that should not be stretched.
The center content of the texture will be stretched.

Patch structure::

left right
+------+-----------------+------+
| (1) | (2) | (3) |
| | | |
+------+-----------------+------+ top
| (4) | (5) | (6) |
| | | |
| | | |
| | | |
| | | |
| | | |
+------+-----------------+------+ bottom
| (7) | (8) | (9) |
| | | |
+------+-----------------+------+

To summarize, the texture will be stretched in the following ways:
* Areas (1), (3), (7) and (9) will not be stretched.
* Area (5) will be stretched horizontally and vertically.
* Areas (2) and (8) will be stretched horizontally.
* Areas (4) and (6) will be stretched vertically.

:param int left: The left border of the 9-patch (in pixels)
:param int right: The right border of the 9-patch (in pixels)
:param int bottom: The bottom border of the 9-patch (in pixels)
:param int top: The top border of the 9-patch (in pixels)
:param Texture texture: The texture used for the 9-patch
:param TextureAtlas atlas: the atlas which the texture belongs to (defaults to arcades default atlas)
Keeps borders & corners at constant widths while stretching the middle.

It can be used with new or existing :py:class:`~arcade.gui.UIWidget`
subclasses wherever an ordinary :py:class:`arcade.Texture` is
supported. This is useful for GUI elements which must grow or shrink
while keeping their border decorations constant, such as dialog boxes
or text boxes.

The diagram below explains the stretching behavior of this class:

* Numbered regions with arrows (``<--->``) stretch along the
direction(s) of any arrows present
* bars (``|---|``) mark the distances specified by the border
parameters (``left``, etc)

.. code-block::
:caption: Stretch Axes & Border Parameters

left right
|------| |------|
top
+------+-----------------+------+ ---
| (1) | (2) | (3) | |
| | <-------------> | | |
+------+-----------------+------+ ---
| (4) | (5) ^ | (6) |
| ^ | | | ^ |
| | | | | | |
| | | <------+------> | | |
| | | | | | |
| | | | | | |
| v | v | v |
+------+-----------------+------+ ---
| (7) | (8) | (9) | |
| | <-------------> | | |
+------+-----------------+------+ ---
bottom

As the texture is stretched, the numbered slices of the texture behave
as follows:

* Areas ``(1)``, ``(3)``, ``(7)`` and ``(9)`` never stretch.
* Area ``(5)`` stretches both horizontally and vertically.
* Areas ``(2)`` and ``(8)`` only stretch horizontally.
* Areas ``(4)`` and ``(6)`` only stretch vertically.

:param int left: The width of the left border of the 9-patch
(in pixels)
:param int right: The width of the right border of the 9-patch
(in pixels)
:param int bottom: The height of the bottom border of the 9-patch
(in pixels)
:param int top: The height of the top border of the 9-patch
(in pixels)
:param Texture texture: The raw texture to use for the 9-patch
:param TextureAtlas atlas: Specify an atlas other than arcade's default
texture atlas
"""

def __init__(
Expand Down Expand Up @@ -99,7 +121,8 @@ def texture(self, texture: arcade.Texture):
def program(self) -> gl.program.Program:
"""
Get or set the shader program.
Returns the default shader if no shader is assigned.

Returns the default shader if no other shader is assigned.
"""
return self._program

Expand All @@ -110,7 +133,8 @@ def program(self, program: gl.program.Program):
def _set_texture(self, texture: arcade.Texture):
"""
Internal method for setting the texture.
It simply ensures the texture is added to the global atlas

It ensures the texture is added to the global atlas.
"""
if not self._atlas.has_texture(texture):
self._atlas.add(texture)
Expand Down Expand Up @@ -154,9 +178,7 @@ def top(self, top: int):

@property
def size(self) -> Tuple[int, int]:
"""
Get size of texture.
"""
"""The size of texture as a width, height tuple in pixels."""
return self.texture.size

@property
Expand All @@ -172,17 +194,24 @@ def height(self) -> int:
def draw_sized(
self,
*,
position: Tuple[float, float] = (0, 0),
position: Tuple[float, float] = (0.0, 0.0),
size: Tuple[float, float],
pixelated: bool = False,
**kwargs
):
"""
Draw the 9-patch.
Draw the 9-patch texture with a specific size.

.. warning:: This method assumes the passed dimensions are proper!

:param size: size of the 9-patch
Unexpected behavior may occur if you specify a size
smaller than the total size of the border areas.


:param position: Bottom left offset of the texture in pixels
:param size: Size of the 9-patch as width, height in pixels
:param pixelated: Whether to draw with nearest neighbor interpolation
"""
# TODO support to draw at a given position
self.program.set_uniform_safe(
"texture_id", self._atlas.get_texture_id(self._texture.atlas_name)
)
Expand All @@ -202,9 +231,7 @@ def draw_sized(
self._geometry.render(self._program, vertices=1)

def _check_sizes(self):
"""
Check if borders are valid
"""
"""Raise a ValueError if any dimension is invalid."""
# Sanity check values
if self._left < 0:
raise ValueError("Left border must be a positive integer")
Expand All @@ -217,6 +244,8 @@ def _check_sizes(self):

# Sanity check texture size
if self._left + self._right > self._texture.width:
raise ValueError("Left and right border must be smaller than texture width")
raise ValueError(
"Left and right border must be smaller than texture width")
if self._bottom + self._top > self._texture.height:
raise ValueError("Bottom and top border must be smaller than texture height")
raise ValueError(
"Bottom and top border must be smaller than texture height")