pytermgui.widgets.pixel_matrix

The module containing all the widgets that can be used to display pixel-based data.

View Source
"""
The module containing all the widgets that can be used to display
pixel-based data.
"""

from __future__ import annotations

from .base import Widget
from ..parser import markup
from ..regex import real_length
from ..ansi_interface import MouseEvent, MouseAction

__all__ = [
    "PixelMatrix",
    "DensePixelMatrix",
]


class PixelMatrix(Widget):
    """A matrix of pixels.

    The way this object should be used is by accessing & modifying
    the underlying matrix. This can be done using the set & getitem
    syntacies:

    ```python3
    from pytermgui import PixelMatrix

    matrix = PixelMatrix(10, 10, default="white")
    for y in matrix.rows:
        for x in matrix.columns:
            matrix[y, x] = "black"
    ```

    The above snippet draws a black diagonal going from the top left
    to bottom right.

    Each item of the rows should be a single PyTermGUI-parsable color
    string. For more information about this, see
    `pytermgui.ansi_interface.Color`.
    """

    selected_pixel: tuple[tuple[int, int], str] | None
    """A tuple of the position & value (color) of the currently hovered pixel."""

    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
        """Initializes a PixelMatrix.

        Args:
            width: The amount of columns the matrix will have.
            height: The amount of rows the matrix will have.
            default: The default color to use to initialize the matrix with.
        """

        super().__init__(**attrs)

        self.rows = height
        self.columns = width

        self._matrix = []

        for _ in range(self.rows):
            self._matrix.append([default] * self.columns)

        self.selected_pixel = None
        self.build()

    @classmethod
    def from_matrix(cls, matrix: list[list[str]]) -> PixelMatrix:
        """Creates a PixelMatrix from the given matrix.

        The given matrix should be a list of rows, each containing a number
        of cells. It is optimal for all rows to share the same amount of cells.

        Args:
            matrix: The matrix to use. This is a list of lists of strings
                with each element representing a PyTermGUI-parseable color.

        Returns:
            A new type(self).
        """

        obj = cls(max(len(row) for row in matrix), len(matrix))
        setattr(obj, "_matrix", matrix)
        obj.build()

        return obj

    def _update_dimensions(self, lines: list[str]):
        """Updates the dimensions of this matrix.

        Args:
            lines: A list of lines that the calculations will be based upon.
        """

        self.static_width = max(real_length(line) for line in lines)
        self.height = len(lines)

    def handle_mouse(self, event: MouseEvent) -> bool:
        """Handles a mouse event.

        On hover, the `selected_pixel` attribute is set to the current pixel.
        """

        if event.action is MouseAction.HOVER:
            xoffset = event.position[0] - self.pos[0]
            yoffset = event.position[1] - self.pos[1]

            color = self._matrix[yoffset][xoffset // 2]

            self.selected_pixel = ((xoffset // 2, yoffset), color)
            return True

        return False

    def get_lines(self) -> list[str]:
        """Returns lines built by the `build` method."""

        return self._lines

    def build(self) -> list[str]:
        """Builds the image pixels.

        Returns:
            The lines that this object will return, until a subsequent `build` call.
            These lines are stored in the `self._lines` variable.
        """

        lines: list[str] = []
        for row in self._matrix:
            line = ""
            for pixel in row:
                if len(pixel) > 0:
                    line += f"[@{pixel}]  "
                else:
                    line += "[/]  "

            lines.append(markup.parse(line))

        self._lines = lines
        self._update_dimensions(lines)

        return lines

    def __getitem__(self, indices: tuple[int, int]) -> str:
        """Gets a matrix item."""

        posy, posx = indices
        return self._matrix[posy][posx]

    def __setitem__(self, indices: tuple[int, int], value: str) -> None:
        """Sets a matrix item."""

        posy, posx = indices
        self._matrix[posy][posx] = value


class DensePixelMatrix(PixelMatrix):
    """A more dense (2x) PixelMatrix.

    Due to each pixel only occupying 1/2 characters in height, accurately
    determining selected_pixel is impossible, thus the functionality does
    not exist here.
    """

    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
        """Initializes DensePixelMatrix.

        Args:
            width: The width of the matrix.
            height: The height of the matrix.
            default: The default color to use to initialize the matrix with.
        """

        super().__init__(width, height, default, **attrs)

        self.width = width // 2

    def handle_mouse(self, event: MouseEvent) -> bool:
        """As mentioned in the class documentation, mouse handling is disabled here."""

        return False

    def build(self) -> list[str]:
        """Builds the image pixels, using half-block characters.

        Returns:
            The lines that this object will return, until a subsequent `build` call.
            These lines are stored in the `self._lines` variable.
        """

        lines = []
        lines_to_zip: list[list[str]] = []
        for row in self._matrix:
            lines_to_zip.append(row)
            if len(lines_to_zip) != 2:
                continue

            line = ""
            top_row, bottom_row = lines_to_zip[0], lines_to_zip[1]
            for bottom, top in zip(bottom_row, top_row):
                if len(top) + len(bottom) == 0:
                    line += " "
                    continue

                if bottom == "":
                    line += markup.parse(f"[{top}]▀")
                    continue

                markup_str = "@" + top + " " if len(top) > 0 else ""

                markup_str += bottom
                line += markup.parse(f"[{markup_str}]▄")

            lines.append(line)
            lines_to_zip = []

        self._lines = lines
        self._update_dimensions(lines)

        return lines
#   class PixelMatrix(pytermgui.widgets.base.Widget):
View Source
class PixelMatrix(Widget):
    """A matrix of pixels.

    The way this object should be used is by accessing & modifying
    the underlying matrix. This can be done using the set & getitem
    syntacies:

    ```python3
    from pytermgui import PixelMatrix

    matrix = PixelMatrix(10, 10, default="white")
    for y in matrix.rows:
        for x in matrix.columns:
            matrix[y, x] = "black"
    ```

    The above snippet draws a black diagonal going from the top left
    to bottom right.

    Each item of the rows should be a single PyTermGUI-parsable color
    string. For more information about this, see
    `pytermgui.ansi_interface.Color`.
    """

    selected_pixel: tuple[tuple[int, int], str] | None
    """A tuple of the position & value (color) of the currently hovered pixel."""

    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
        """Initializes a PixelMatrix.

        Args:
            width: The amount of columns the matrix will have.
            height: The amount of rows the matrix will have.
            default: The default color to use to initialize the matrix with.
        """

        super().__init__(**attrs)

        self.rows = height
        self.columns = width

        self._matrix = []

        for _ in range(self.rows):
            self._matrix.append([default] * self.columns)

        self.selected_pixel = None
        self.build()

    @classmethod
    def from_matrix(cls, matrix: list[list[str]]) -> PixelMatrix:
        """Creates a PixelMatrix from the given matrix.

        The given matrix should be a list of rows, each containing a number
        of cells. It is optimal for all rows to share the same amount of cells.

        Args:
            matrix: The matrix to use. This is a list of lists of strings
                with each element representing a PyTermGUI-parseable color.

        Returns:
            A new type(self).
        """

        obj = cls(max(len(row) for row in matrix), len(matrix))
        setattr(obj, "_matrix", matrix)
        obj.build()

        return obj

    def _update_dimensions(self, lines: list[str]):
        """Updates the dimensions of this matrix.

        Args:
            lines: A list of lines that the calculations will be based upon.
        """

        self.static_width = max(real_length(line) for line in lines)
        self.height = len(lines)

    def handle_mouse(self, event: MouseEvent) -> bool:
        """Handles a mouse event.

        On hover, the `selected_pixel` attribute is set to the current pixel.
        """

        if event.action is MouseAction.HOVER:
            xoffset = event.position[0] - self.pos[0]
            yoffset = event.position[1] - self.pos[1]

            color = self._matrix[yoffset][xoffset // 2]

            self.selected_pixel = ((xoffset // 2, yoffset), color)
            return True

        return False

    def get_lines(self) -> list[str]:
        """Returns lines built by the `build` method."""

        return self._lines

    def build(self) -> list[str]:
        """Builds the image pixels.

        Returns:
            The lines that this object will return, until a subsequent `build` call.
            These lines are stored in the `self._lines` variable.
        """

        lines: list[str] = []
        for row in self._matrix:
            line = ""
            for pixel in row:
                if len(pixel) > 0:
                    line += f"[@{pixel}]  "
                else:
                    line += "[/]  "

            lines.append(markup.parse(line))

        self._lines = lines
        self._update_dimensions(lines)

        return lines

    def __getitem__(self, indices: tuple[int, int]) -> str:
        """Gets a matrix item."""

        posy, posx = indices
        return self._matrix[posy][posx]

    def __setitem__(self, indices: tuple[int, int], value: str) -> None:
        """Sets a matrix item."""

        posy, posx = indices
        self._matrix[posy][posx] = value

A matrix of pixels.

The way this object should be used is by accessing & modifying the underlying matrix. This can be done using the set & getitem syntacies:

from pytermgui import PixelMatrix

matrix = PixelMatrix(10, 10, default="white")
for y in matrix.rows:
    for x in matrix.columns:
        matrix[y, x] = "black"

The above snippet draws a black diagonal going from the top left to bottom right.

Each item of the rows should be a single PyTermGUI-parsable color string. For more information about this, see pytermgui.ansi_interface.Color.

#   PixelMatrix(width: int, height: int, default: str = '', **attrs)
View Source
    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
        """Initializes a PixelMatrix.

        Args:
            width: The amount of columns the matrix will have.
            height: The amount of rows the matrix will have.
            default: The default color to use to initialize the matrix with.
        """

        super().__init__(**attrs)

        self.rows = height
        self.columns = width

        self._matrix = []

        for _ in range(self.rows):
            self._matrix.append([default] * self.columns)

        self.selected_pixel = None
        self.build()

Initializes a PixelMatrix.

Args
  • width: The amount of columns the matrix will have.
  • height: The amount of rows the matrix will have.
  • default: The default color to use to initialize the matrix with.
#   selected_pixel: tuple[tuple[int, int], str] | None

A tuple of the position & value (color) of the currently hovered pixel.

#  
@classmethod
def from_matrix( cls, matrix: list[list[str]] ) -> pytermgui.widgets.pixel_matrix.PixelMatrix:
View Source
    @classmethod
    def from_matrix(cls, matrix: list[list[str]]) -> PixelMatrix:
        """Creates a PixelMatrix from the given matrix.

        The given matrix should be a list of rows, each containing a number
        of cells. It is optimal for all rows to share the same amount of cells.

        Args:
            matrix: The matrix to use. This is a list of lists of strings
                with each element representing a PyTermGUI-parseable color.

        Returns:
            A new type(self).
        """

        obj = cls(max(len(row) for row in matrix), len(matrix))
        setattr(obj, "_matrix", matrix)
        obj.build()

        return obj

Creates a PixelMatrix from the given matrix.

The given matrix should be a list of rows, each containing a number of cells. It is optimal for all rows to share the same amount of cells.

Args
  • matrix: The matrix to use. This is a list of lists of strings with each element representing a PyTermGUI-parseable color.
Returns

A new type(self).

#   def handle_mouse(self, event: pytermgui.ansi_interface.MouseEvent) -> bool:
View Source
    def handle_mouse(self, event: MouseEvent) -> bool:
        """Handles a mouse event.

        On hover, the `selected_pixel` attribute is set to the current pixel.
        """

        if event.action is MouseAction.HOVER:
            xoffset = event.position[0] - self.pos[0]
            yoffset = event.position[1] - self.pos[1]

            color = self._matrix[yoffset][xoffset // 2]

            self.selected_pixel = ((xoffset // 2, yoffset), color)
            return True

        return False

Handles a mouse event.

On hover, the selected_pixel attribute is set to the current pixel.

#   def get_lines(self) -> list[str]:
View Source
    def get_lines(self) -> list[str]:
        """Returns lines built by the `build` method."""

        return self._lines

Returns lines built by the build method.

#   def build(self) -> list[str]:
View Source
    def build(self) -> list[str]:
        """Builds the image pixels.

        Returns:
            The lines that this object will return, until a subsequent `build` call.
            These lines are stored in the `self._lines` variable.
        """

        lines: list[str] = []
        for row in self._matrix:
            line = ""
            for pixel in row:
                if len(pixel) > 0:
                    line += f"[@{pixel}]  "
                else:
                    line += "[/]  "

            lines.append(markup.parse(line))

        self._lines = lines
        self._update_dimensions(lines)

        return lines

Builds the image pixels.

Returns

The lines that this object will return, until a subsequent build call. These lines are stored in the self._lines variable.

#   class DensePixelMatrix(PixelMatrix):
View Source
class DensePixelMatrix(PixelMatrix):
    """A more dense (2x) PixelMatrix.

    Due to each pixel only occupying 1/2 characters in height, accurately
    determining selected_pixel is impossible, thus the functionality does
    not exist here.
    """

    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
        """Initializes DensePixelMatrix.

        Args:
            width: The width of the matrix.
            height: The height of the matrix.
            default: The default color to use to initialize the matrix with.
        """

        super().__init__(width, height, default, **attrs)

        self.width = width // 2

    def handle_mouse(self, event: MouseEvent) -> bool:
        """As mentioned in the class documentation, mouse handling is disabled here."""

        return False

    def build(self) -> list[str]:
        """Builds the image pixels, using half-block characters.

        Returns:
            The lines that this object will return, until a subsequent `build` call.
            These lines are stored in the `self._lines` variable.
        """

        lines = []
        lines_to_zip: list[list[str]] = []
        for row in self._matrix:
            lines_to_zip.append(row)
            if len(lines_to_zip) != 2:
                continue

            line = ""
            top_row, bottom_row = lines_to_zip[0], lines_to_zip[1]
            for bottom, top in zip(bottom_row, top_row):
                if len(top) + len(bottom) == 0:
                    line += " "
                    continue

                if bottom == "":
                    line += markup.parse(f"[{top}]▀")
                    continue

                markup_str = "@" + top + " " if len(top) > 0 else ""

                markup_str += bottom
                line += markup.parse(f"[{markup_str}]▄")

            lines.append(line)
            lines_to_zip = []

        self._lines = lines
        self._update_dimensions(lines)

        return lines

A more dense (2x) PixelMatrix.

Due to each pixel only occupying 1/2 characters in height, accurately determining selected_pixel is impossible, thus the functionality does not exist here.

#   DensePixelMatrix(width: int, height: int, default: str = '', **attrs)
View Source
    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
        """Initializes DensePixelMatrix.

        Args:
            width: The width of the matrix.
            height: The height of the matrix.
            default: The default color to use to initialize the matrix with.
        """

        super().__init__(width, height, default, **attrs)

        self.width = width // 2

Initializes DensePixelMatrix.

Args
  • width: The width of the matrix.
  • height: The height of the matrix.
  • default: The default color to use to initialize the matrix with.
#   def handle_mouse(self, event: pytermgui.ansi_interface.MouseEvent) -> bool:
View Source
    def handle_mouse(self, event: MouseEvent) -> bool:
        """As mentioned in the class documentation, mouse handling is disabled here."""

        return False

As mentioned in the class documentation, mouse handling is disabled here.

#   def build(self) -> list[str]:
View Source
    def build(self) -> list[str]:
        """Builds the image pixels, using half-block characters.

        Returns:
            The lines that this object will return, until a subsequent `build` call.
            These lines are stored in the `self._lines` variable.
        """

        lines = []
        lines_to_zip: list[list[str]] = []
        for row in self._matrix:
            lines_to_zip.append(row)
            if len(lines_to_zip) != 2:
                continue

            line = ""
            top_row, bottom_row = lines_to_zip[0], lines_to_zip[1]
            for bottom, top in zip(bottom_row, top_row):
                if len(top) + len(bottom) == 0:
                    line += " "
                    continue

                if bottom == "":
                    line += markup.parse(f"[{top}]▀")
                    continue

                markup_str = "@" + top + " " if len(top) > 0 else ""

                markup_str += bottom
                line += markup.parse(f"[{markup_str}]▄")

            lines.append(line)
            lines_to_zip = []

        self._lines = lines
        self._update_dimensions(lines)

        return lines

Builds the image pixels, using half-block characters.

Returns

The lines that this object will return, until a subsequent build call. These lines are stored in the self._lines variable.