pytermgui.widgets.pixel_matrix

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

  1"""
  2The module containing all the widgets that can be used to display
  3pixel-based data.
  4"""
  5
  6from __future__ import annotations
  7
  8from .base import Widget
  9from ..parser import markup
 10from ..regex import real_length
 11from ..ansi_interface import MouseEvent, MouseAction
 12
 13__all__ = [
 14    "PixelMatrix",
 15    "DensePixelMatrix",
 16]
 17
 18
 19class PixelMatrix(Widget):
 20    """A matrix of pixels.
 21
 22    The way this object should be used is by accessing & modifying
 23    the underlying matrix. This can be done using the set & getitem
 24    syntacies:
 25
 26    ```python3
 27    from pytermgui import PixelMatrix
 28
 29    matrix = PixelMatrix(10, 10, default="white")
 30    for y in matrix.rows:
 31        for x in matrix.columns:
 32            matrix[y, x] = "black"
 33    ```
 34
 35    The above snippet draws a black diagonal going from the top left
 36    to bottom right.
 37
 38    Each item of the rows should be a single PyTermGUI-parsable color
 39    string. For more information about this, see
 40    `pytermgui.ansi_interface.Color`.
 41    """
 42
 43    selected_pixel: tuple[tuple[int, int], str] | None
 44    """A tuple of the position & value (color) of the currently hovered pixel."""
 45
 46    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
 47        """Initializes a PixelMatrix.
 48
 49        Args:
 50            width: The amount of columns the matrix will have.
 51            height: The amount of rows the matrix will have.
 52            default: The default color to use to initialize the matrix with.
 53        """
 54
 55        super().__init__(**attrs)
 56
 57        self.rows = height
 58        self.columns = width
 59
 60        self._matrix = []
 61
 62        for _ in range(self.rows):
 63            self._matrix.append([default] * self.columns)
 64
 65        self.selected_pixel = None
 66        self.build()
 67
 68    @classmethod
 69    def from_matrix(cls, matrix: list[list[str]]) -> PixelMatrix:
 70        """Creates a PixelMatrix from the given matrix.
 71
 72        The given matrix should be a list of rows, each containing a number
 73        of cells. It is optimal for all rows to share the same amount of cells.
 74
 75        Args:
 76            matrix: The matrix to use. This is a list of lists of strings
 77                with each element representing a PyTermGUI-parseable color.
 78
 79        Returns:
 80            A new type(self).
 81        """
 82
 83        obj = cls(max(len(row) for row in matrix), len(matrix))
 84        setattr(obj, "_matrix", matrix)
 85        obj.build()
 86
 87        return obj
 88
 89    def _update_dimensions(self, lines: list[str]):
 90        """Updates the dimensions of this matrix.
 91
 92        Args:
 93            lines: A list of lines that the calculations will be based upon.
 94        """
 95
 96        self.static_width = max(real_length(line) for line in lines)
 97        self.height = len(lines)
 98
 99    def handle_mouse(self, event: MouseEvent) -> bool:
100        """Handles a mouse event.
101
102        On hover, the `selected_pixel` attribute is set to the current pixel.
103        """
104
105        if event.action is MouseAction.HOVER:
106            xoffset = event.position[0] - self.pos[0]
107            yoffset = event.position[1] - self.pos[1]
108
109            color = self._matrix[yoffset][xoffset // 2]
110
111            self.selected_pixel = ((xoffset // 2, yoffset), color)
112            return True
113
114        return False
115
116    def get_lines(self) -> list[str]:
117        """Returns lines built by the `build` method."""
118
119        return self._lines
120
121    def build(self) -> list[str]:
122        """Builds the image pixels.
123
124        Returns:
125            The lines that this object will return, until a subsequent `build` call.
126            These lines are stored in the `self._lines` variable.
127        """
128
129        lines: list[str] = []
130        for row in self._matrix:
131            line = ""
132            for pixel in row:
133                if len(pixel) > 0:
134                    line += f"[@{pixel}]  "
135                else:
136                    line += "[/]  "
137
138            lines.append(markup.parse(line))
139
140        self._lines = lines
141        self._update_dimensions(lines)
142
143        return lines
144
145    def __getitem__(self, indices: tuple[int, int]) -> str:
146        """Gets a matrix item."""
147
148        posy, posx = indices
149        return self._matrix[posy][posx]
150
151    def __setitem__(self, indices: tuple[int, int], value: str) -> None:
152        """Sets a matrix item."""
153
154        posy, posx = indices
155        self._matrix[posy][posx] = value
156
157
158class DensePixelMatrix(PixelMatrix):
159    """A more dense (2x) PixelMatrix.
160
161    Due to each pixel only occupying 1/2 characters in height, accurately
162    determining selected_pixel is impossible, thus the functionality does
163    not exist here.
164    """
165
166    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
167        """Initializes DensePixelMatrix.
168
169        Args:
170            width: The width of the matrix.
171            height: The height of the matrix.
172            default: The default color to use to initialize the matrix with.
173        """
174
175        super().__init__(width, height, default, **attrs)
176
177        self.width = width // 2
178
179    def handle_mouse(self, event: MouseEvent) -> bool:
180        """As mentioned in the class documentation, mouse handling is disabled here."""
181
182        return False
183
184    def build(self) -> list[str]:
185        """Builds the image pixels, using half-block characters.
186
187        Returns:
188            The lines that this object will return, until a subsequent `build` call.
189            These lines are stored in the `self._lines` variable.
190        """
191
192        lines = []
193        lines_to_zip: list[list[str]] = []
194        for row in self._matrix:
195            lines_to_zip.append(row)
196            if len(lines_to_zip) != 2:
197                continue
198
199            line = ""
200            top_row, bottom_row = lines_to_zip[0], lines_to_zip[1]
201            for bottom, top in zip(bottom_row, top_row):
202                if len(top) + len(bottom) == 0:
203                    line += " "
204                    continue
205
206                if bottom == "":
207                    line += markup.parse(f"[{top}]▀")
208                    continue
209
210                markup_str = "@" + top + " " if len(top) > 0 else ""
211
212                markup_str += bottom
213                line += markup.parse(f"[{markup_str}]▄")
214
215            lines.append(line)
216            lines_to_zip = []
217
218        self._lines = lines
219        self._update_dimensions(lines)
220
221        return lines
class PixelMatrix(pytermgui.widgets.base.Widget):
 20class PixelMatrix(Widget):
 21    """A matrix of pixels.
 22
 23    The way this object should be used is by accessing & modifying
 24    the underlying matrix. This can be done using the set & getitem
 25    syntacies:
 26
 27    ```python3
 28    from pytermgui import PixelMatrix
 29
 30    matrix = PixelMatrix(10, 10, default="white")
 31    for y in matrix.rows:
 32        for x in matrix.columns:
 33            matrix[y, x] = "black"
 34    ```
 35
 36    The above snippet draws a black diagonal going from the top left
 37    to bottom right.
 38
 39    Each item of the rows should be a single PyTermGUI-parsable color
 40    string. For more information about this, see
 41    `pytermgui.ansi_interface.Color`.
 42    """
 43
 44    selected_pixel: tuple[tuple[int, int], str] | None
 45    """A tuple of the position & value (color) of the currently hovered pixel."""
 46
 47    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
 48        """Initializes a PixelMatrix.
 49
 50        Args:
 51            width: The amount of columns the matrix will have.
 52            height: The amount of rows the matrix will have.
 53            default: The default color to use to initialize the matrix with.
 54        """
 55
 56        super().__init__(**attrs)
 57
 58        self.rows = height
 59        self.columns = width
 60
 61        self._matrix = []
 62
 63        for _ in range(self.rows):
 64            self._matrix.append([default] * self.columns)
 65
 66        self.selected_pixel = None
 67        self.build()
 68
 69    @classmethod
 70    def from_matrix(cls, matrix: list[list[str]]) -> PixelMatrix:
 71        """Creates a PixelMatrix from the given matrix.
 72
 73        The given matrix should be a list of rows, each containing a number
 74        of cells. It is optimal for all rows to share the same amount of cells.
 75
 76        Args:
 77            matrix: The matrix to use. This is a list of lists of strings
 78                with each element representing a PyTermGUI-parseable color.
 79
 80        Returns:
 81            A new type(self).
 82        """
 83
 84        obj = cls(max(len(row) for row in matrix), len(matrix))
 85        setattr(obj, "_matrix", matrix)
 86        obj.build()
 87
 88        return obj
 89
 90    def _update_dimensions(self, lines: list[str]):
 91        """Updates the dimensions of this matrix.
 92
 93        Args:
 94            lines: A list of lines that the calculations will be based upon.
 95        """
 96
 97        self.static_width = max(real_length(line) for line in lines)
 98        self.height = len(lines)
 99
100    def handle_mouse(self, event: MouseEvent) -> bool:
101        """Handles a mouse event.
102
103        On hover, the `selected_pixel` attribute is set to the current pixel.
104        """
105
106        if event.action is MouseAction.HOVER:
107            xoffset = event.position[0] - self.pos[0]
108            yoffset = event.position[1] - self.pos[1]
109
110            color = self._matrix[yoffset][xoffset // 2]
111
112            self.selected_pixel = ((xoffset // 2, yoffset), color)
113            return True
114
115        return False
116
117    def get_lines(self) -> list[str]:
118        """Returns lines built by the `build` method."""
119
120        return self._lines
121
122    def build(self) -> list[str]:
123        """Builds the image pixels.
124
125        Returns:
126            The lines that this object will return, until a subsequent `build` call.
127            These lines are stored in the `self._lines` variable.
128        """
129
130        lines: list[str] = []
131        for row in self._matrix:
132            line = ""
133            for pixel in row:
134                if len(pixel) > 0:
135                    line += f"[@{pixel}]  "
136                else:
137                    line += "[/]  "
138
139            lines.append(markup.parse(line))
140
141        self._lines = lines
142        self._update_dimensions(lines)
143
144        return lines
145
146    def __getitem__(self, indices: tuple[int, int]) -> str:
147        """Gets a matrix item."""
148
149        posy, posx = indices
150        return self._matrix[posy][posx]
151
152    def __setitem__(self, indices: tuple[int, int], value: str) -> None:
153        """Sets a matrix item."""
154
155        posy, posx = indices
156        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)
47    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
48        """Initializes a PixelMatrix.
49
50        Args:
51            width: The amount of columns the matrix will have.
52            height: The amount of rows the matrix will have.
53            default: The default color to use to initialize the matrix with.
54        """
55
56        super().__init__(**attrs)
57
58        self.rows = height
59        self.columns = width
60
61        self._matrix = []
62
63        for _ in range(self.rows):
64            self._matrix.append([default] * self.columns)
65
66        self.selected_pixel = None
67        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:
69    @classmethod
70    def from_matrix(cls, matrix: list[list[str]]) -> PixelMatrix:
71        """Creates a PixelMatrix from the given matrix.
72
73        The given matrix should be a list of rows, each containing a number
74        of cells. It is optimal for all rows to share the same amount of cells.
75
76        Args:
77            matrix: The matrix to use. This is a list of lists of strings
78                with each element representing a PyTermGUI-parseable color.
79
80        Returns:
81            A new type(self).
82        """
83
84        obj = cls(max(len(row) for row in matrix), len(matrix))
85        setattr(obj, "_matrix", matrix)
86        obj.build()
87
88        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:
100    def handle_mouse(self, event: MouseEvent) -> bool:
101        """Handles a mouse event.
102
103        On hover, the `selected_pixel` attribute is set to the current pixel.
104        """
105
106        if event.action is MouseAction.HOVER:
107            xoffset = event.position[0] - self.pos[0]
108            yoffset = event.position[1] - self.pos[1]
109
110            color = self._matrix[yoffset][xoffset // 2]
111
112            self.selected_pixel = ((xoffset // 2, yoffset), color)
113            return True
114
115        return False

Handles a mouse event.

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

def get_lines(self) -> list[str]:
117    def get_lines(self) -> list[str]:
118        """Returns lines built by the `build` method."""
119
120        return self._lines

Returns lines built by the build method.

def build(self) -> list[str]:
122    def build(self) -> list[str]:
123        """Builds the image pixels.
124
125        Returns:
126            The lines that this object will return, until a subsequent `build` call.
127            These lines are stored in the `self._lines` variable.
128        """
129
130        lines: list[str] = []
131        for row in self._matrix:
132            line = ""
133            for pixel in row:
134                if len(pixel) > 0:
135                    line += f"[@{pixel}]  "
136                else:
137                    line += "[/]  "
138
139            lines.append(markup.parse(line))
140
141        self._lines = lines
142        self._update_dimensions(lines)
143
144        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):
159class DensePixelMatrix(PixelMatrix):
160    """A more dense (2x) PixelMatrix.
161
162    Due to each pixel only occupying 1/2 characters in height, accurately
163    determining selected_pixel is impossible, thus the functionality does
164    not exist here.
165    """
166
167    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
168        """Initializes DensePixelMatrix.
169
170        Args:
171            width: The width of the matrix.
172            height: The height of the matrix.
173            default: The default color to use to initialize the matrix with.
174        """
175
176        super().__init__(width, height, default, **attrs)
177
178        self.width = width // 2
179
180    def handle_mouse(self, event: MouseEvent) -> bool:
181        """As mentioned in the class documentation, mouse handling is disabled here."""
182
183        return False
184
185    def build(self) -> list[str]:
186        """Builds the image pixels, using half-block characters.
187
188        Returns:
189            The lines that this object will return, until a subsequent `build` call.
190            These lines are stored in the `self._lines` variable.
191        """
192
193        lines = []
194        lines_to_zip: list[list[str]] = []
195        for row in self._matrix:
196            lines_to_zip.append(row)
197            if len(lines_to_zip) != 2:
198                continue
199
200            line = ""
201            top_row, bottom_row = lines_to_zip[0], lines_to_zip[1]
202            for bottom, top in zip(bottom_row, top_row):
203                if len(top) + len(bottom) == 0:
204                    line += " "
205                    continue
206
207                if bottom == "":
208                    line += markup.parse(f"[{top}]▀")
209                    continue
210
211                markup_str = "@" + top + " " if len(top) > 0 else ""
212
213                markup_str += bottom
214                line += markup.parse(f"[{markup_str}]▄")
215
216            lines.append(line)
217            lines_to_zip = []
218
219        self._lines = lines
220        self._update_dimensions(lines)
221
222        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)
167    def __init__(self, width: int, height: int, default: str = "", **attrs) -> None:
168        """Initializes DensePixelMatrix.
169
170        Args:
171            width: The width of the matrix.
172            height: The height of the matrix.
173            default: The default color to use to initialize the matrix with.
174        """
175
176        super().__init__(width, height, default, **attrs)
177
178        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:
180    def handle_mouse(self, event: MouseEvent) -> bool:
181        """As mentioned in the class documentation, mouse handling is disabled here."""
182
183        return False

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

def build(self) -> list[str]:
185    def build(self) -> list[str]:
186        """Builds the image pixels, using half-block characters.
187
188        Returns:
189            The lines that this object will return, until a subsequent `build` call.
190            These lines are stored in the `self._lines` variable.
191        """
192
193        lines = []
194        lines_to_zip: list[list[str]] = []
195        for row in self._matrix:
196            lines_to_zip.append(row)
197            if len(lines_to_zip) != 2:
198                continue
199
200            line = ""
201            top_row, bottom_row = lines_to_zip[0], lines_to_zip[1]
202            for bottom, top in zip(bottom_row, top_row):
203                if len(top) + len(bottom) == 0:
204                    line += " "
205                    continue
206
207                if bottom == "":
208                    line += markup.parse(f"[{top}]▀")
209                    continue
210
211                markup_str = "@" + top + " " if len(top) > 0 else ""
212
213                markup_str += bottom
214                line += markup.parse(f"[{markup_str}]▄")
215
216            lines.append(line)
217            lines_to_zip = []
218
219        self._lines = lines
220        self._update_dimensions(lines)
221
222        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.