From f977156ff510f1fa8c921f5c5a617f68f8cde42d Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 17:02:57 +0200 Subject: [PATCH 01/13] Make arcade.Text lazy --- arcade/text.py | 403 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 298 insertions(+), 105 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index 9ab51b21df..eaf202cd60 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -276,8 +276,9 @@ def __init__( z: float = 0, **kwargs, ): - # Raises a RuntimeError if no window for better user feedback - arcade.get_window() + self._initialized = False + self.arguments = [text, x, y, color, font_size, width, align, font_name, bold, italic, anchor_x, anchor_y, multiline, rotation, batch, group, z] + self.kwargs = kwargs if align not in ("left", "center", "right"): raise ValueError("The 'align' parameter must be equal to 'left', 'right', or 'center'.") @@ -288,42 +289,64 @@ def __init__( f"but got {width!r}." ) - adjusted_font = _attempt_font_name_resolution(font_name) + try: + self._init_deferred() + except Exception: + self._initialized = False + + def initialize(self) -> None: + """ + Manually initialize the Text if it was lazy loaded. + This has no effect if the Text was already initialized. + """ + if self._initialized: + return + self._init_deferred() + def _init_deferred(self): + """ + Deferred initialization when lazy loaded + """ + + self.arguments[7] = _attempt_font_name_resolution(self.arguments[7]) self._label = pyglet.text.Label( - text=text, - # pyglet is lying about what it takes here and float is entirely valid - x=x, # type: ignore - y=y, # type: ignore - z=z, # type: ignore - font_name=adjusted_font, - # TODO: Fix this upstream (Mac & Linux seem to allow float) - font_size=font_size, # type: ignore - # use type: ignore since cast is slow & pyglet used Literal - anchor_x=anchor_x, # type: ignore - anchor_y=anchor_y, # type: ignore - color=Color.from_iterable(color), - width=width, - align=align, # type: ignore - weight=pyglet.text.Weight.BOLD if bold else pyglet.text.Weight.NORMAL, - italic=italic, - multiline=multiline, - rotation=rotation, - # type: ignore # pending https://github.com/pyglet/pyglet/issues/843 - batch=batch, - group=group, - **kwargs, + text=self.arguments[0], + x=self.arguments[1], + y=self.arguments[2], + color=Color.from_iterable(self.arguments[3]), + font_size=self.arguments[4], + width=self.arguments[5], + align=self.arguments[6], + font_name=self.arguments[7], + weight=pyglet.text.Weight.BOLD if self.arguments[8] else pyglet.text.Weight.NORMAL, + italic=self.arguments[9], + anchor_x=self.arguments[10], + anchor_y=self.arguments[11], + multiline=self.arguments[12], + rotation=self.arguments[13], + batch=self.arguments[14], + group=self.arguments[15], + z=self.arguments[16], + **self.kwargs, ) + self._initialized = True + def __enter__(self): """ Update multiple attributes of this text, using efficient update mechanism of the underlying ``pyglet.Label`` """ - self._label.begin_update() + if self._initialized: + self._label.begin_update() + else: + raise RuntimeError("Text must be initialized before entering the context.") def __exit__(self, exc_type, exc_val, exc_tb): - self._label.end_update() + if self._initialized: + self._label.end_update() + else: + raise RuntimeError("Text must be initialized before exiting the context.") @property def batch(self) -> pyglet.graphics.Batch | None: @@ -331,11 +354,17 @@ def batch(self) -> pyglet.graphics.Batch | None: Can be unset by setting to ``None``. """ - return self._label.batch + if self._initialized: + return self._label.batch + else: + raise RuntimeError("Text must be initialized before accessing the batch.") @batch.setter def batch(self, batch: pyglet.graphics.Batch): - self._label.batch = batch + if self._initialized: + self._label.batch = batch + else: + raise RuntimeError("Text must be initialized before setting the batch.") @property def group(self) -> pyglet.graphics.Group | None: @@ -346,11 +375,17 @@ def group(self) -> pyglet.graphics.Group | None: batching very large sets of text needing to separate into groups or even mix with other pyglet batch content. """ - return self._label.group + if self._initialized: + return self._label.group + else: + raise RuntimeError("Text must be initialized before accessing the group.") @group.setter def group(self, group: pyglet.graphics.Group): - self._label.group = group + if self._initialized: + self._label.group = group + else: + raise RuntimeError("Text must be initialized before setting the group.") @property def value(self) -> str: @@ -359,14 +394,20 @@ def value(self) -> str: The value assigned will be converted to a string. """ - return self._label.text + if self._initialized: + return self._label.text + else: + raise RuntimeError("Text must be initialized before accessing the text.") @value.setter def value(self, value: Any): value = str(value) - if self._label.text == value: - return - self._label.text = value + if self._initialized: + if self._label.text == value: + return + self._label.text = value + else: + raise RuntimeError("Text must be initialized before setting the text.") @property def text(self) -> str: @@ -377,71 +418,117 @@ def text(self) -> str: This is an alias for :py:attr:`~arcade.Text.value` """ - return self._label.text + if self._initialized: + return self._label.text + else: + raise RuntimeError("Text must be initialized before accessing the text.") @text.setter def text(self, value: Any): value = str(value) - if self._label.text == value: - return - self._label.text = value + if self._initialized: + if self._label.text == value: + return + self._label.text = value + else: + raise RuntimeError("Text must be initialized before setting the text.") @property def x(self) -> float: """Get or set the x position of the label.""" - return self._label.x + if self._initialized: + return self._label.x + else: + raise RuntimeError("Text must be initialized before accessing the x position.") @x.setter def x(self, x: float) -> None: - if self._label.x == x: - return - self._label.x = x + if self._initialized: + if self._label.x == x: + return + self._label.x = x + else: + raise RuntimeError("Text must be initialized before setting the x position.") @property def y(self) -> float: """Get or set the y position of the label.""" - return self._label.y + if self._initialized: + return self._label.y + else: + raise RuntimeError("Text must be initialized before accessing the y position.") @y.setter def y(self, y: float): - if self._label.y == y: - return - self._label.y = y + if self._initialized: + if self._label.y == y: + return + self._label.y = y + else: + raise RuntimeError("Text must be initialized before setting the y position.") @property def z(self) -> float: """Get or set the z position of the label.""" - return self._label.z + if self._initialized: + return self._label.z + else: + raise RuntimeError("Text must be initialized before accessing the z position.") @z.setter def z(self, z: float): - if self._label.z == z: - return - self._label.z = z + if self._initialized: + if self._label.z == z: + return + self._label.z = z + else: + raise RuntimeError("Text must be initialized before setting the z position.") @property def font_name(self) -> FontNameOrNames: """Get or set the font name(s) for the label.""" - if not isinstance(self._label.font_name, str): - return tuple(self._label.font_name) + if self._initialized: + if not isinstance(self._label.font_name, str): + return tuple(self._label.font_name) + else: + return self._label.font_name else: - return self._label.font_name + raise RuntimeError("Text must be initialized before accessing the font name.") @font_name.setter def font_name(self, font_name: FontNameOrNames) -> None: - if isinstance(font_name, str): - self._label.font_name = font_name + if self._initialized: + if isinstance(font_name, str): + self._label.font_name = font_name + else: + self._label.font_name = list(font_name) else: - self._label.font_name = list(font_name) + raise RuntimeError("Text must be initialized before setting the font name.") + + @font_name.setter + def font_name(self, font_name: FontNameOrNames) -> None: + if self._initialized: + if isinstance(font_name, str): + self._label.font_name = font_name + else: + self._label.font_name = list(font_name) + else: + raise RuntimeError("Text must be initialized before setting the font name.") @property def font_size(self) -> float: """Get or set the font size of the label.""" - return self._label.font_size + if self._initialized: + return self._label.font_size + else: + raise RuntimeError("Text must be initialized before accessing the font size.") @font_size.setter - def font_size(self, font_size: float): - self._label.font_size = font_size + def font_size(self, font_size: float) -> None: + if self._initialized: + self._label.font_size = font_size + else: + raise RuntimeError("Text must be initialized before setting the font size.") @property def anchor_x(self) -> str: @@ -450,11 +537,17 @@ def anchor_x(self) -> str: Options: ``"left"``, ``"center"``, or ``"right"`` """ - return self._label.anchor_x + if self._initialized: + return self._label.anchor_x + else: + raise RuntimeError("Text must be initialized before accessing the anchor x.") @anchor_x.setter - def anchor_x(self, anchor_x: str): - self._label.anchor_x = anchor_x # type: ignore + def anchor_x(self, anchor_x: str) -> None: + if self._initialized: + self._label.anchor_x = anchor_x # type: ignore + else: + raise RuntimeError("Text must be initialized before setting the anchor x.") @property def anchor_y(self) -> str: @@ -463,29 +556,47 @@ def anchor_y(self) -> str: Options : ``"top"``, ``"bottom"``, ``"center"``, or ``"baseline"`` """ - return self._label.anchor_y + if self._initialized: + return self._label.anchor_y + else: + raise RuntimeError("Text must be initialized before accessing the anchor y.") @anchor_y.setter - def anchor_y(self, anchor_y: str): - self._label.anchor_y = anchor_y # type: ignore + def anchor_y(self, anchor_y: str) -> None: + if self._initialized: + self._label.anchor_y = anchor_y # type: ignore + else: + raise RuntimeError("Text must be initialized before setting the anchor y.") @property def rotation(self) -> float: """Get or set the clockwise rotation""" - return self._label.rotation + if self._initialized: + return self._label.rotation + else: + raise RuntimeError("Text must be initialized before accessing the rotation.") @rotation.setter - def rotation(self, rotation: float): - self._label.rotation = rotation + def rotation(self, rotation: float) -> None: + if self._initialized: + self._label.rotation = rotation + else: + raise RuntimeError("Text must be initialized before setting the rotation.") @property def color(self) -> Color: """Get or set the text color for the label.""" - return Color.from_iterable(self._label.color) + if self._initialized: + return Color.from_iterable(self._label.color) + else: + raise RuntimeError("Text must be initialized before accessing the color.") @color.setter - def color(self, color: RGBOrA255): - self._label.color = Color.from_iterable(color) + def color(self, color: RGBOrA255) -> None: + if self._initialized: + self._label.color = Color.from_iterable(color) + else: + raise RuntimeError("Text must be initialized before setting the color.") @property def width(self) -> int | None: @@ -496,11 +607,17 @@ def width(self) -> int | None: If you are looking for the physical size if the text, see :py:attr:`~arcade.Text.content_width` """ - return self._label.width + if self._initialized: + return self._label.width + else: + raise RuntimeError("Text must be initialized before accessing the width.") @width.setter - def width(self, width: int): - self._label.width = width + def width(self, width: int) -> None: + if self._initialized: + self._label.width = width + else: + raise RuntimeError("Text must be initialized before setting the width.") @property def height(self) -> int | None: @@ -511,51 +628,81 @@ def height(self) -> int | None: If you are looking for the physical size if the text, see :py:attr:`~arcade.Text.content_height` """ - return self._label.height + if self._initialized: + return self._label.height + else: + raise RuntimeError("Text must be initialized before accessing the height.") @height.setter - def height(self, value: int): - self._label.height = value + def height(self, value: int) -> None: + if self._initialized: + self._label.height = value + else: + raise RuntimeError("Text must be initialized before setting the height.") @property - def size(self): + def size(self) -> tuple[int, int]: """Get the size of the label.""" - return self._label.width, self._label.height + if self._initialized: + return self._label.width, self._label.height + else: + raise RuntimeError("Text must be initialized before accessing the size.") @property def content_width(self) -> int: """Get the pixel width of the text contents.""" - return self._label.content_width + if self._initialized: + return self._label.content_width + else: + raise RuntimeError("Text must be initialized before accessing the content width.") @property def content_height(self) -> int: """Get the pixel height of the text content.""" - return self._label.content_height + if self._initialized: + return self._label.content_height + else: + raise RuntimeError("Text must be initialized before accessing the content height.") @property def left(self) -> float: """Pixel location of the left content border.""" - return self._label.left + if self._initialized: + return self._label.left + else: + raise RuntimeError("Text must be initialized before accessing the left content border.") @property def right(self) -> float: """Pixel location of the right content border.""" - return self._label.right + if self._initialized: + return self._label.right + else: + raise RuntimeError("Text must be initialized before accessing the right content border.") @property def top(self) -> float: """Pixel location of the top content border.""" - return self._label.top + if self._initialized: + return self._label.top + else: + raise RuntimeError("Text must be initialized before accessing the top content border.") @property def bottom(self) -> float: """Pixel location of the bottom content border.""" - return self._label.bottom + if self._initialized: + return self._label.bottom + else: + raise RuntimeError("Text must be initialized before accessing the bottom content border.") @property def content_size(self) -> tuple[int, int]: """Get the pixel width and height of the text contents.""" - return self._label.content_width, self._label.content_height + if self._initialized: + return self._label.content_width, self._label.content_height + else: + raise RuntimeError("Text must be initialized before accessing the content size.") @property def align(self) -> str: @@ -563,11 +710,17 @@ def align(self) -> str: Valid options: ``"left"``, ``"center"``, ``"right"``. """ - return self._label.get_style("align") # type: ignore + if self._initialized: + return self._label.get_style("align") # type: ignore + else: + raise RuntimeError("Text must be initialized before accessing the align property.") @align.setter def align(self, align: str): - self._label.set_style("align", align) + if self._initialized: + self._label.set_style("align", align) + else: + raise RuntimeError("Text must be initialized before setting the align property.") @property def bold(self) -> bool | str: @@ -583,29 +736,47 @@ def bold(self) -> bool | str: * ``"light"`` """ - return self._label.weight == pyglet.text.Weight.BOLD + if self._initialized: + return self._label.weight == pyglet.text.Weight.BOLD + else: + raise RuntimeError("Text must be initialized before accessing the bold property.") @bold.setter def bold(self, bold: bool | str): - self._label.weight = pyglet.text.Weight.BOLD if bold else pyglet.text.Weight.NORMAL + if self._initialized: + self._label.weight = pyglet.text.Weight.BOLD if bold else pyglet.text.Weight.NORMAL + else: + raise RuntimeError("Text must be initialized before setting the bold property.") @property def italic(self) -> bool | str: """Get or set the italic state of the label.""" - return self._label.italic + if self._initialized: + return self._label.italic + else: + raise RuntimeError("Text must be initialized before accessing the italic property.") @italic.setter def italic(self, italic: bool | str): - self._label.italic = italic + if self._initialized: + self._label.italic = italic + else: + raise RuntimeError("Text must be initialized before setting the italic property.") @property def multiline(self) -> bool: """Get or set the multiline flag of the label.""" - return self._label.multiline + if self._initialized: + return self._label.multiline + else: + raise RuntimeError("Text must be initialized before accessing the multiline property.") @multiline.setter def multiline(self, multiline: bool): - self._label.multiline = multiline + if self._initialized: + self._label.multiline = multiline + else: + raise RuntimeError("Text must be initialized before setting the multiline property.") def draw(self) -> None: """ @@ -618,6 +789,8 @@ def draw(self) -> None: instance. For information on how to do this, see :ref:`sprite_move_scrolling`. """ + if not self._initialized: + self._init_deferred() _draw_pyglet_label(self._label) def draw_debug( @@ -635,6 +808,8 @@ def draw_debug( background_color: Color the content background outline_color: Color of the content outline """ + if not self._initialized: + self._init_deferred() left = self.left right = self.right top = self.top @@ -659,17 +834,23 @@ def position(self) -> Point: This is faster than setting x and y position separately because the underlying geometry only needs to change position once. """ - return self._label.x, self._label.y + if self._initialized: + return self._label.x, self._label.y + else: + raise RuntimeError("Text must be initialized before accessing the position.") @position.setter def position(self, point: Point): # Starting with Pyglet 2.0b2 label positions take a z parameter. - x, y, *z = point + if self._initialized: + x, y, *z = point - if z: - self._label.position = x, y, z[0] + if z: + self._label.position = x, y, z[0] + else: + self._label.position = x, y, self._label.z else: - self._label.position = x, y, self._label.z + raise RuntimeError("Text must be initialized before setting the position.") @property def tracking(self) -> float | None: @@ -683,26 +864,38 @@ def tracking(self) -> float | None: Returns: a pixel amount, or None if the tracking is inconsistent. """ - kerning = self._label.get_style("kerning") - return kerning if kerning != pyglet.text.document.STYLE_INDETERMINATE else None + if self._initialized: + kerning = self._label.get_style("kerning") + return kerning if kerning != pyglet.text.document.STYLE_INDETERMINATE else None + else: + raise RuntimeError("Text must be initialized before accessing the tracking.") @tracking.setter def tracking(self, value: float): - self._label.set_style("kerning", value) + if self._initialized: + self._label.set_style("kerning", value) + else: + raise RuntimeError("Text must be initialized before setting the tracking.") def em_to_px(self, em: float) -> float: """Convert from an em value to a pixel amount. 1em is defined as ``font_size`` pt. """ - return (em * self.font_size) * (4 / 3) + if not self._initialized: + return (em * self.font_size) * (4 / 3) + else: + raise RuntimeError("Text must be initialized before converting from em to px.") def px_to_em(self, px: float) -> float: """Convert from a pixel amount to a value in ems. 1em is defined as ``font_size`` pt. """ - return px / (4 / 3) / self.font_size + if not self._initialized: + return px / (4 / 3) / self.font_size + else: + raise RuntimeError("Text must be initialized before converting from px to em.") def create_text_sprite( From 36f8882960389828190226d2dcc7458579fbd6bc Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 17:12:37 +0200 Subject: [PATCH 02/13] Fix size function return type --- arcade/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index eaf202cd60..bded4628be 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -641,7 +641,7 @@ def height(self, value: int) -> None: raise RuntimeError("Text must be initialized before setting the height.") @property - def size(self) -> tuple[int, int]: + def size(self) -> tuple[int, int] | None: """Get the size of the label.""" if self._initialized: return self._label.width, self._label.height From 0b5f4644d71e3621a50b28785caa98526b13a4a8 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 17:14:48 +0200 Subject: [PATCH 03/13] Try to fix size function return type --- arcade/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index bded4628be..278c6c2c1f 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -641,7 +641,7 @@ def height(self, value: int) -> None: raise RuntimeError("Text must be initialized before setting the height.") @property - def size(self) -> tuple[int, int] | None: + def size(self) -> tuple[int | None, int | None]: """Get the size of the label.""" if self._initialized: return self._label.width, self._label.height From ca8a0d0286b361258392b30d0ec1831c40656a88 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 17:19:43 +0200 Subject: [PATCH 04/13] Make code lines shorter to fix typing errors --- arcade/text.py | 116 ++++++++++++++++++++++++------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index 278c6c2c1f..92782fedee 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -277,7 +277,8 @@ def __init__( **kwargs, ): self._initialized = False - self.arguments = [text, x, y, color, font_size, width, align, font_name, bold, italic, anchor_x, anchor_y, multiline, rotation, batch, group, z] + self.arguments = [text, x, y, color, font_size, width, align, font_name, bold, + italic, anchor_x, anchor_y, multiline, rotation, batch, group, z] self.kwargs = kwargs if align not in ("left", "center", "right"): @@ -340,13 +341,13 @@ def __enter__(self): if self._initialized: self._label.begin_update() else: - raise RuntimeError("Text must be initialized before entering the context.") + raise RuntimeError("Text must be initialized") def __exit__(self, exc_type, exc_val, exc_tb): if self._initialized: self._label.end_update() else: - raise RuntimeError("Text must be initialized before exiting the context.") + raise RuntimeError("Text must be initialized") @property def batch(self) -> pyglet.graphics.Batch | None: @@ -357,14 +358,14 @@ def batch(self) -> pyglet.graphics.Batch | None: if self._initialized: return self._label.batch else: - raise RuntimeError("Text must be initialized before accessing the batch.") + raise RuntimeError("Text must be initialized") @batch.setter def batch(self, batch: pyglet.graphics.Batch): if self._initialized: self._label.batch = batch else: - raise RuntimeError("Text must be initialized before setting the batch.") + raise RuntimeError("Text must be initialized") @property def group(self) -> pyglet.graphics.Group | None: @@ -378,14 +379,14 @@ def group(self) -> pyglet.graphics.Group | None: if self._initialized: return self._label.group else: - raise RuntimeError("Text must be initialized before accessing the group.") + raise RuntimeError("Text must be initialized") @group.setter def group(self, group: pyglet.graphics.Group): if self._initialized: self._label.group = group else: - raise RuntimeError("Text must be initialized before setting the group.") + raise RuntimeError("Text must be initialized") @property def value(self) -> str: @@ -397,7 +398,7 @@ def value(self) -> str: if self._initialized: return self._label.text else: - raise RuntimeError("Text must be initialized before accessing the text.") + raise RuntimeError("Text must be initialized") @value.setter def value(self, value: Any): @@ -407,7 +408,7 @@ def value(self, value: Any): return self._label.text = value else: - raise RuntimeError("Text must be initialized before setting the text.") + raise RuntimeError("Text must be initialized") @property def text(self) -> str: @@ -421,7 +422,7 @@ def text(self) -> str: if self._initialized: return self._label.text else: - raise RuntimeError("Text must be initialized before accessing the text.") + raise RuntimeError("Text must be initialized") @text.setter def text(self, value: Any): @@ -431,7 +432,7 @@ def text(self, value: Any): return self._label.text = value else: - raise RuntimeError("Text must be initialized before setting the text.") + raise RuntimeError("Text must be initialized") @property def x(self) -> float: @@ -439,7 +440,7 @@ def x(self) -> float: if self._initialized: return self._label.x else: - raise RuntimeError("Text must be initialized before accessing the x position.") + raise RuntimeError("Text must be initialized") @x.setter def x(self, x: float) -> None: @@ -448,7 +449,7 @@ def x(self, x: float) -> None: return self._label.x = x else: - raise RuntimeError("Text must be initialized before setting the x position.") + raise RuntimeError("Text must be initialized") @property def y(self) -> float: @@ -456,7 +457,7 @@ def y(self) -> float: if self._initialized: return self._label.y else: - raise RuntimeError("Text must be initialized before accessing the y position.") + raise RuntimeError("Text must be initialized") @y.setter def y(self, y: float): @@ -465,7 +466,7 @@ def y(self, y: float): return self._label.y = y else: - raise RuntimeError("Text must be initialized before setting the y position.") + raise RuntimeError("Text must be initialized") @property def z(self) -> float: @@ -473,7 +474,7 @@ def z(self) -> float: if self._initialized: return self._label.z else: - raise RuntimeError("Text must be initialized before accessing the z position.") + raise RuntimeError("Text must be initialized") @z.setter def z(self, z: float): @@ -482,7 +483,7 @@ def z(self, z: float): return self._label.z = z else: - raise RuntimeError("Text must be initialized before setting the z position.") + raise RuntimeError("Text must be initialized") @property def font_name(self) -> FontNameOrNames: @@ -493,7 +494,7 @@ def font_name(self) -> FontNameOrNames: else: return self._label.font_name else: - raise RuntimeError("Text must be initialized before accessing the font name.") + raise RuntimeError("Text must be initialized") @font_name.setter def font_name(self, font_name: FontNameOrNames) -> None: @@ -503,7 +504,7 @@ def font_name(self, font_name: FontNameOrNames) -> None: else: self._label.font_name = list(font_name) else: - raise RuntimeError("Text must be initialized before setting the font name.") + raise RuntimeError("Text must be initialized") @font_name.setter def font_name(self, font_name: FontNameOrNames) -> None: @@ -513,7 +514,7 @@ def font_name(self, font_name: FontNameOrNames) -> None: else: self._label.font_name = list(font_name) else: - raise RuntimeError("Text must be initialized before setting the font name.") + raise RuntimeError("Text must be initialized") @property def font_size(self) -> float: @@ -521,14 +522,14 @@ def font_size(self) -> float: if self._initialized: return self._label.font_size else: - raise RuntimeError("Text must be initialized before accessing the font size.") + raise RuntimeError("Text must be initialized") @font_size.setter def font_size(self, font_size: float) -> None: if self._initialized: self._label.font_size = font_size else: - raise RuntimeError("Text must be initialized before setting the font size.") + raise RuntimeError("Text must be initialized") @property def anchor_x(self) -> str: @@ -540,14 +541,14 @@ def anchor_x(self) -> str: if self._initialized: return self._label.anchor_x else: - raise RuntimeError("Text must be initialized before accessing the anchor x.") + raise RuntimeError("Text must be initialized") @anchor_x.setter def anchor_x(self, anchor_x: str) -> None: if self._initialized: self._label.anchor_x = anchor_x # type: ignore else: - raise RuntimeError("Text must be initialized before setting the anchor x.") + raise RuntimeError("Text must be initialized") @property def anchor_y(self) -> str: @@ -559,14 +560,14 @@ def anchor_y(self) -> str: if self._initialized: return self._label.anchor_y else: - raise RuntimeError("Text must be initialized before accessing the anchor y.") + raise RuntimeError("Text must be initialized") @anchor_y.setter def anchor_y(self, anchor_y: str) -> None: if self._initialized: self._label.anchor_y = anchor_y # type: ignore else: - raise RuntimeError("Text must be initialized before setting the anchor y.") + raise RuntimeError("Text must be initialized") @property def rotation(self) -> float: @@ -574,14 +575,14 @@ def rotation(self) -> float: if self._initialized: return self._label.rotation else: - raise RuntimeError("Text must be initialized before accessing the rotation.") + raise RuntimeError("Text must be initialized") @rotation.setter def rotation(self, rotation: float) -> None: if self._initialized: self._label.rotation = rotation else: - raise RuntimeError("Text must be initialized before setting the rotation.") + raise RuntimeError("Text must be initialized") @property def color(self) -> Color: @@ -589,14 +590,14 @@ def color(self) -> Color: if self._initialized: return Color.from_iterable(self._label.color) else: - raise RuntimeError("Text must be initialized before accessing the color.") + raise RuntimeError("Text must be initialized") @color.setter def color(self, color: RGBOrA255) -> None: if self._initialized: self._label.color = Color.from_iterable(color) else: - raise RuntimeError("Text must be initialized before setting the color.") + raise RuntimeError("Text must be initialized") @property def width(self) -> int | None: @@ -610,14 +611,14 @@ def width(self) -> int | None: if self._initialized: return self._label.width else: - raise RuntimeError("Text must be initialized before accessing the width.") + raise RuntimeError("Text must be initialized") @width.setter def width(self, width: int) -> None: if self._initialized: self._label.width = width else: - raise RuntimeError("Text must be initialized before setting the width.") + raise RuntimeError("Text must be initialized") @property def height(self) -> int | None: @@ -631,22 +632,22 @@ def height(self) -> int | None: if self._initialized: return self._label.height else: - raise RuntimeError("Text must be initialized before accessing the height.") + raise RuntimeError("Text must be initialized") @height.setter def height(self, value: int) -> None: if self._initialized: self._label.height = value else: - raise RuntimeError("Text must be initialized before setting the height.") + raise RuntimeError("Text must be initialized") @property - def size(self) -> tuple[int | None, int | None]: + def size(self) -> tuple[int, int] | None: """Get the size of the label.""" if self._initialized: return self._label.width, self._label.height else: - raise RuntimeError("Text must be initialized before accessing the size.") + raise RuntimeError("Text must be initialized") @property def content_width(self) -> int: @@ -654,7 +655,7 @@ def content_width(self) -> int: if self._initialized: return self._label.content_width else: - raise RuntimeError("Text must be initialized before accessing the content width.") + raise RuntimeError("Text must be initialized") @property def content_height(self) -> int: @@ -662,7 +663,7 @@ def content_height(self) -> int: if self._initialized: return self._label.content_height else: - raise RuntimeError("Text must be initialized before accessing the content height.") + raise RuntimeError("Text must be initialized") @property def left(self) -> float: @@ -670,7 +671,7 @@ def left(self) -> float: if self._initialized: return self._label.left else: - raise RuntimeError("Text must be initialized before accessing the left content border.") + raise RuntimeError("Text must be initialized") @property def right(self) -> float: @@ -678,7 +679,7 @@ def right(self) -> float: if self._initialized: return self._label.right else: - raise RuntimeError("Text must be initialized before accessing the right content border.") + raise RuntimeError("Text must be initialized") @property def top(self) -> float: @@ -686,7 +687,7 @@ def top(self) -> float: if self._initialized: return self._label.top else: - raise RuntimeError("Text must be initialized before accessing the top content border.") + raise RuntimeError("Text must be initialized") @property def bottom(self) -> float: @@ -694,7 +695,7 @@ def bottom(self) -> float: if self._initialized: return self._label.bottom else: - raise RuntimeError("Text must be initialized before accessing the bottom content border.") + raise RuntimeError("Text must be initialized") @property def content_size(self) -> tuple[int, int]: @@ -702,7 +703,7 @@ def content_size(self) -> tuple[int, int]: if self._initialized: return self._label.content_width, self._label.content_height else: - raise RuntimeError("Text must be initialized before accessing the content size.") + raise RuntimeError("Text must be initialized") @property def align(self) -> str: @@ -713,14 +714,14 @@ def align(self) -> str: if self._initialized: return self._label.get_style("align") # type: ignore else: - raise RuntimeError("Text must be initialized before accessing the align property.") + raise RuntimeError("Text must be initialized") @align.setter def align(self, align: str): if self._initialized: self._label.set_style("align", align) else: - raise RuntimeError("Text must be initialized before setting the align property.") + raise RuntimeError("Text must be initialized") @property def bold(self) -> bool | str: @@ -739,14 +740,14 @@ def bold(self) -> bool | str: if self._initialized: return self._label.weight == pyglet.text.Weight.BOLD else: - raise RuntimeError("Text must be initialized before accessing the bold property.") + raise RuntimeError("Text must be initialized") @bold.setter def bold(self, bold: bool | str): if self._initialized: self._label.weight = pyglet.text.Weight.BOLD if bold else pyglet.text.Weight.NORMAL else: - raise RuntimeError("Text must be initialized before setting the bold property.") + raise RuntimeError("Text must be initialized") @property def italic(self) -> bool | str: @@ -754,14 +755,14 @@ def italic(self) -> bool | str: if self._initialized: return self._label.italic else: - raise RuntimeError("Text must be initialized before accessing the italic property.") + raise RuntimeError("Text must be initialized") @italic.setter def italic(self, italic: bool | str): if self._initialized: self._label.italic = italic else: - raise RuntimeError("Text must be initialized before setting the italic property.") + raise RuntimeError("Text must be initialized") @property def multiline(self) -> bool: @@ -769,14 +770,14 @@ def multiline(self) -> bool: if self._initialized: return self._label.multiline else: - raise RuntimeError("Text must be initialized before accessing the multiline property.") + raise RuntimeError("Text must be initialized") @multiline.setter def multiline(self, multiline: bool): if self._initialized: self._label.multiline = multiline else: - raise RuntimeError("Text must be initialized before setting the multiline property.") + raise RuntimeError("Text must be initialized") def draw(self) -> None: """ @@ -837,7 +838,7 @@ def position(self) -> Point: if self._initialized: return self._label.x, self._label.y else: - raise RuntimeError("Text must be initialized before accessing the position.") + raise RuntimeError("Text must be initialized") @position.setter def position(self, point: Point): @@ -850,7 +851,7 @@ def position(self, point: Point): else: self._label.position = x, y, self._label.z else: - raise RuntimeError("Text must be initialized before setting the position.") + raise RuntimeError("Text must be initialized") @property def tracking(self) -> float | None: @@ -868,14 +869,14 @@ def tracking(self) -> float | None: kerning = self._label.get_style("kerning") return kerning if kerning != pyglet.text.document.STYLE_INDETERMINATE else None else: - raise RuntimeError("Text must be initialized before accessing the tracking.") + raise RuntimeError("Text must be initialized") @tracking.setter def tracking(self, value: float): if self._initialized: self._label.set_style("kerning", value) else: - raise RuntimeError("Text must be initialized before setting the tracking.") + raise RuntimeError("Text must be initialized") def em_to_px(self, em: float) -> float: """Convert from an em value to a pixel amount. @@ -885,7 +886,7 @@ def em_to_px(self, em: float) -> float: if not self._initialized: return (em * self.font_size) * (4 / 3) else: - raise RuntimeError("Text must be initialized before converting from em to px.") + raise RuntimeError("Text must be initialized") def px_to_em(self, px: float) -> float: """Convert from a pixel amount to a value in ems. @@ -895,8 +896,7 @@ def px_to_em(self, px: float) -> float: if not self._initialized: return px / (4 / 3) / self.font_size else: - raise RuntimeError("Text must be initialized before converting from px to em.") - + raise RuntimeError("Text must be initialized") def create_text_sprite( text: str, From 6ab821bd55a600366ce5033ac12a67116784a788 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 17:22:04 +0200 Subject: [PATCH 05/13] remove trailing whitespace and fix type of size function --- arcade/text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index 92782fedee..d60f68e631 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -277,7 +277,7 @@ def __init__( **kwargs, ): self._initialized = False - self.arguments = [text, x, y, color, font_size, width, align, font_name, bold, + self.arguments = [text, x, y, color, font_size, width, align, font_name, bold, italic, anchor_x, anchor_y, multiline, rotation, batch, group, z] self.kwargs = kwargs @@ -642,7 +642,7 @@ def height(self, value: int) -> None: raise RuntimeError("Text must be initialized") @property - def size(self) -> tuple[int, int] | None: + def size(self) -> tuple[int | None, int | None]: """Get the size of the label.""" if self._initialized: return self._label.width, self._label.height From e4a3a85581db83ee82d76327de850e9615825acc Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 17:51:44 +0200 Subject: [PATCH 06/13] Add the label property and make arguments private - Add the label property and use it in the code, if its not initialized, raise a RuntimeError. - Make the `arguments` and `kwargs` private, using _ --- arcade/text.py | 400 +++++++++++++++---------------------------------- 1 file changed, 117 insertions(+), 283 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index d60f68e631..9f77f39217 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -194,7 +194,7 @@ class Text: The text instances an also be modified while in the batch such as changing the text value, position, or color. - The constructor arguments work identically to those of + The constructor _arguments work identically to those of :py:func:`~arcade.draw_text`. See its documentation for in-depth explanation for how to use each of them. For example code, see :ref:`drawing_text_objects`. @@ -223,7 +223,7 @@ class Text: group: The specific group in a a batch to add the text to (for batch rendering text) - All constructor arguments other than ``text`` have a corresponding + All constructor _arguments other than ``text`` have a corresponding property. To access the current text, use the ``value`` property instead. @@ -277,9 +277,9 @@ def __init__( **kwargs, ): self._initialized = False - self.arguments = [text, x, y, color, font_size, width, align, font_name, bold, - italic, anchor_x, anchor_y, multiline, rotation, batch, group, z] - self.kwargs = kwargs + self._arguments = [text, x, y, color, font_size, width, align, font_name, bold, + italic, anchor_x, anchor_y, multiline, rotation, batch, group, z] + self._kwargs = kwargs if align not in ("left", "center", "right"): raise ValueError("The 'align' parameter must be equal to 'left', 'right', or 'center'.") @@ -309,45 +309,49 @@ def _init_deferred(self): Deferred initialization when lazy loaded """ - self.arguments[7] = _attempt_font_name_resolution(self.arguments[7]) - self._label = pyglet.text.Label( - text=self.arguments[0], - x=self.arguments[1], - y=self.arguments[2], - color=Color.from_iterable(self.arguments[3]), - font_size=self.arguments[4], - width=self.arguments[5], - align=self.arguments[6], - font_name=self.arguments[7], - weight=pyglet.text.Weight.BOLD if self.arguments[8] else pyglet.text.Weight.NORMAL, - italic=self.arguments[9], - anchor_x=self.arguments[10], - anchor_y=self.arguments[11], - multiline=self.arguments[12], - rotation=self.arguments[13], - batch=self.arguments[14], - group=self.arguments[15], - z=self.arguments[16], - **self.kwargs, + self._arguments[7] = _attempt_font_name_resolution(self._arguments[7]) + self.label = pyglet.text.Label( + text=self._arguments[0], + x=self._arguments[1], + y=self._arguments[2], + color=Color.from_iterable(self._arguments[3]), + font_size=self._arguments[4], + width=self._arguments[5], + align=self._arguments[6], + font_name=self._arguments[7], + weight=pyglet.text.Weight.BOLD if self._arguments[8] else pyglet.text.Weight.NORMAL, + italic=self._arguments[9], + anchor_x=self._arguments[10], + anchor_y=self._arguments[11], + multiline=self._arguments[12], + rotation=self._arguments[13], + batch=self._arguments[14], + group=self._arguments[15], + z=self._arguments[16], + **self._kwargs, ) self._initialized = True + @property + def label(self) -> pyglet.text.Label | None: + """ + The underlying pyglet.Label instance. + """ + if self._initialized: + return self._label + else: + raise RuntimeError("Text has not been initialized.") + def __enter__(self): """ Update multiple attributes of this text, using efficient update mechanism of the underlying ``pyglet.Label`` """ - if self._initialized: - self._label.begin_update() - else: - raise RuntimeError("Text must be initialized") + self.label.begin_update() def __exit__(self, exc_type, exc_val, exc_tb): - if self._initialized: - self._label.end_update() - else: - raise RuntimeError("Text must be initialized") + self.label.end_update() @property def batch(self) -> pyglet.graphics.Batch | None: @@ -355,17 +359,11 @@ def batch(self) -> pyglet.graphics.Batch | None: Can be unset by setting to ``None``. """ - if self._initialized: - return self._label.batch - else: - raise RuntimeError("Text must be initialized") + return self.label.batch @batch.setter def batch(self, batch: pyglet.graphics.Batch): - if self._initialized: - self._label.batch = batch - else: - raise RuntimeError("Text must be initialized") + self.label.batch = batch @property def group(self) -> pyglet.graphics.Group | None: @@ -376,17 +374,11 @@ def group(self) -> pyglet.graphics.Group | None: batching very large sets of text needing to separate into groups or even mix with other pyglet batch content. """ - if self._initialized: - return self._label.group - else: - raise RuntimeError("Text must be initialized") + return self.label.group @group.setter def group(self, group: pyglet.graphics.Group): - if self._initialized: - self._label.group = group - else: - raise RuntimeError("Text must be initialized") + self.label.group = group @property def value(self) -> str: @@ -395,20 +387,14 @@ def value(self) -> str: The value assigned will be converted to a string. """ - if self._initialized: - return self._label.text - else: - raise RuntimeError("Text must be initialized") + return self.label.text @value.setter def value(self, value: Any): value = str(value) - if self._initialized: - if self._label.text == value: - return - self._label.text = value - else: - raise RuntimeError("Text must be initialized") + if self.label.text == value: + return + self.label.text = value @property def text(self) -> str: @@ -419,117 +405,71 @@ def text(self) -> str: This is an alias for :py:attr:`~arcade.Text.value` """ - if self._initialized: - return self._label.text - else: - raise RuntimeError("Text must be initialized") + return self.label.text @text.setter def text(self, value: Any): value = str(value) - if self._initialized: - if self._label.text == value: - return - self._label.text = value - else: - raise RuntimeError("Text must be initialized") + if self.label.text == value: + return + self.label.text = value @property def x(self) -> float: """Get or set the x position of the label.""" - if self._initialized: - return self._label.x - else: - raise RuntimeError("Text must be initialized") + return self.label.x @x.setter def x(self, x: float) -> None: - if self._initialized: - if self._label.x == x: - return - self._label.x = x - else: - raise RuntimeError("Text must be initialized") + if self.label.x == x: + return + self.label.x = x @property def y(self) -> float: """Get or set the y position of the label.""" - if self._initialized: - return self._label.y - else: - raise RuntimeError("Text must be initialized") + return self.label.y @y.setter def y(self, y: float): - if self._initialized: - if self._label.y == y: - return - self._label.y = y - else: - raise RuntimeError("Text must be initialized") + if self.label.y == y: + return + self.label.y = y @property def z(self) -> float: """Get or set the z position of the label.""" - if self._initialized: - return self._label.z - else: - raise RuntimeError("Text must be initialized") + return self.label.z @z.setter def z(self, z: float): - if self._initialized: - if self._label.z == z: - return - self._label.z = z - else: - raise RuntimeError("Text must be initialized") + if self.label.z == z: + return + self.label.z = z @property def font_name(self) -> FontNameOrNames: """Get or set the font name(s) for the label.""" - if self._initialized: - if not isinstance(self._label.font_name, str): - return tuple(self._label.font_name) - else: - return self._label.font_name - else: - raise RuntimeError("Text must be initialized") - - @font_name.setter - def font_name(self, font_name: FontNameOrNames) -> None: - if self._initialized: - if isinstance(font_name, str): - self._label.font_name = font_name - else: - self._label.font_name = list(font_name) + if not isinstance(self.label.font_name, str): + return tuple(self.label.font_name) else: - raise RuntimeError("Text must be initialized") + return self.label.font_name @font_name.setter def font_name(self, font_name: FontNameOrNames) -> None: - if self._initialized: - if isinstance(font_name, str): - self._label.font_name = font_name - else: - self._label.font_name = list(font_name) + if isinstance(font_name, str): + self.label.font_name = font_name else: - raise RuntimeError("Text must be initialized") + self.label.font_name = list(font_name) @property def font_size(self) -> float: """Get or set the font size of the label.""" - if self._initialized: - return self._label.font_size - else: - raise RuntimeError("Text must be initialized") + return self.label.font_size @font_size.setter - def font_size(self, font_size: float) -> None: - if self._initialized: - self._label.font_size = font_size - else: - raise RuntimeError("Text must be initialized") + def font_size(self, font_size: float): + self.label.font_size = font_size @property def anchor_x(self) -> str: @@ -538,17 +478,11 @@ def anchor_x(self) -> str: Options: ``"left"``, ``"center"``, or ``"right"`` """ - if self._initialized: - return self._label.anchor_x - else: - raise RuntimeError("Text must be initialized") + return self.label.anchor_x @anchor_x.setter - def anchor_x(self, anchor_x: str) -> None: - if self._initialized: - self._label.anchor_x = anchor_x # type: ignore - else: - raise RuntimeError("Text must be initialized") + def anchor_x(self, anchor_x: str): + self.label.anchor_x = anchor_x # type: ignore @property def anchor_y(self) -> str: @@ -557,47 +491,29 @@ def anchor_y(self) -> str: Options : ``"top"``, ``"bottom"``, ``"center"``, or ``"baseline"`` """ - if self._initialized: - return self._label.anchor_y - else: - raise RuntimeError("Text must be initialized") + return self.label.anchor_y @anchor_y.setter - def anchor_y(self, anchor_y: str) -> None: - if self._initialized: - self._label.anchor_y = anchor_y # type: ignore - else: - raise RuntimeError("Text must be initialized") + def anchor_y(self, anchor_y: str): + self.label.anchor_y = anchor_y # type: ignore @property def rotation(self) -> float: """Get or set the clockwise rotation""" - if self._initialized: - return self._label.rotation - else: - raise RuntimeError("Text must be initialized") + return self.label.rotation @rotation.setter - def rotation(self, rotation: float) -> None: - if self._initialized: - self._label.rotation = rotation - else: - raise RuntimeError("Text must be initialized") + def rotation(self, rotation: float): + self.label.rotation = rotation @property def color(self) -> Color: """Get or set the text color for the label.""" - if self._initialized: - return Color.from_iterable(self._label.color) - else: - raise RuntimeError("Text must be initialized") + return Color.from_iterable(self.label.color) @color.setter - def color(self, color: RGBOrA255) -> None: - if self._initialized: - self._label.color = Color.from_iterable(color) - else: - raise RuntimeError("Text must be initialized") + def color(self, color: RGBOrA255): + self.label.color = Color.from_iterable(color) @property def width(self) -> int | None: @@ -608,17 +524,11 @@ def width(self) -> int | None: If you are looking for the physical size if the text, see :py:attr:`~arcade.Text.content_width` """ - if self._initialized: - return self._label.width - else: - raise RuntimeError("Text must be initialized") + return self.label.width @width.setter - def width(self, width: int) -> None: - if self._initialized: - self._label.width = width - else: - raise RuntimeError("Text must be initialized") + def width(self, width: int): + self.label.width = width @property def height(self) -> int | None: @@ -629,81 +539,51 @@ def height(self) -> int | None: If you are looking for the physical size if the text, see :py:attr:`~arcade.Text.content_height` """ - if self._initialized: - return self._label.height - else: - raise RuntimeError("Text must be initialized") + return self.label.height @height.setter - def height(self, value: int) -> None: - if self._initialized: - self._label.height = value - else: - raise RuntimeError("Text must be initialized") + def height(self, value: int): + self.label.height = value @property - def size(self) -> tuple[int | None, int | None]: + def size(self): """Get the size of the label.""" - if self._initialized: - return self._label.width, self._label.height - else: - raise RuntimeError("Text must be initialized") + return self.label.width, self.label.height @property def content_width(self) -> int: """Get the pixel width of the text contents.""" - if self._initialized: - return self._label.content_width - else: - raise RuntimeError("Text must be initialized") + return self.label.content_width @property def content_height(self) -> int: """Get the pixel height of the text content.""" - if self._initialized: - return self._label.content_height - else: - raise RuntimeError("Text must be initialized") + return self.label.content_height @property def left(self) -> float: """Pixel location of the left content border.""" - if self._initialized: - return self._label.left - else: - raise RuntimeError("Text must be initialized") + return self.label.left @property def right(self) -> float: """Pixel location of the right content border.""" - if self._initialized: - return self._label.right - else: - raise RuntimeError("Text must be initialized") + return self.label.right @property def top(self) -> float: """Pixel location of the top content border.""" - if self._initialized: - return self._label.top - else: - raise RuntimeError("Text must be initialized") + return self.label.top @property def bottom(self) -> float: """Pixel location of the bottom content border.""" - if self._initialized: - return self._label.bottom - else: - raise RuntimeError("Text must be initialized") + return self.label.bottom @property def content_size(self) -> tuple[int, int]: """Get the pixel width and height of the text contents.""" - if self._initialized: - return self._label.content_width, self._label.content_height - else: - raise RuntimeError("Text must be initialized") + return self.label.content_width, self.label.content_height @property def align(self) -> str: @@ -711,17 +591,11 @@ def align(self) -> str: Valid options: ``"left"``, ``"center"``, ``"right"``. """ - if self._initialized: - return self._label.get_style("align") # type: ignore - else: - raise RuntimeError("Text must be initialized") + return self.label.get_style("align") # type: ignore @align.setter def align(self, align: str): - if self._initialized: - self._label.set_style("align", align) - else: - raise RuntimeError("Text must be initialized") + self.label.set_style("align", align) @property def bold(self) -> bool | str: @@ -737,47 +611,29 @@ def bold(self) -> bool | str: * ``"light"`` """ - if self._initialized: - return self._label.weight == pyglet.text.Weight.BOLD - else: - raise RuntimeError("Text must be initialized") + return self.label.weight == pyglet.text.Weight.BOLD @bold.setter def bold(self, bold: bool | str): - if self._initialized: - self._label.weight = pyglet.text.Weight.BOLD if bold else pyglet.text.Weight.NORMAL - else: - raise RuntimeError("Text must be initialized") + self.label.weight = pyglet.text.Weight.BOLD if bold else pyglet.text.Weight.NORMAL @property def italic(self) -> bool | str: """Get or set the italic state of the label.""" - if self._initialized: - return self._label.italic - else: - raise RuntimeError("Text must be initialized") + return self.label.italic @italic.setter def italic(self, italic: bool | str): - if self._initialized: - self._label.italic = italic - else: - raise RuntimeError("Text must be initialized") + self.label.italic = italic @property def multiline(self) -> bool: """Get or set the multiline flag of the label.""" - if self._initialized: - return self._label.multiline - else: - raise RuntimeError("Text must be initialized") + return self.label.multiline @multiline.setter def multiline(self, multiline: bool): - if self._initialized: - self._label.multiline = multiline - else: - raise RuntimeError("Text must be initialized") + self.label.multiline = multiline def draw(self) -> None: """ @@ -790,9 +646,7 @@ def draw(self) -> None: instance. For information on how to do this, see :ref:`sprite_move_scrolling`. """ - if not self._initialized: - self._init_deferred() - _draw_pyglet_label(self._label) + _draw_pyglet_label(self.label) def draw_debug( self, @@ -809,8 +663,6 @@ def draw_debug( background_color: Color the content background outline_color: Color of the content outline """ - if not self._initialized: - self._init_deferred() left = self.left right = self.right top = self.top @@ -825,7 +677,7 @@ def draw_debug( # Draw anchor arcade.draw_point(self.x, self.y, color=anchor_color, size=6) - _draw_pyglet_label(self._label) + _draw_pyglet_label(self.label) @property def position(self) -> Point: @@ -835,23 +687,17 @@ def position(self) -> Point: This is faster than setting x and y position separately because the underlying geometry only needs to change position once. """ - if self._initialized: - return self._label.x, self._label.y - else: - raise RuntimeError("Text must be initialized") + return self.label.x, self.label.y @position.setter def position(self, point: Point): # Starting with Pyglet 2.0b2 label positions take a z parameter. - if self._initialized: - x, y, *z = point + x, y, *z = point - if z: - self._label.position = x, y, z[0] - else: - self._label.position = x, y, self._label.z + if z: + self.label.position = x, y, z[0] else: - raise RuntimeError("Text must be initialized") + self.label.position = x, y, self.label.z @property def tracking(self) -> float | None: @@ -865,38 +711,26 @@ def tracking(self) -> float | None: Returns: a pixel amount, or None if the tracking is inconsistent. """ - if self._initialized: - kerning = self._label.get_style("kerning") - return kerning if kerning != pyglet.text.document.STYLE_INDETERMINATE else None - else: - raise RuntimeError("Text must be initialized") + kerning = self.label.get_style("kerning") + return kerning if kerning != pyglet.text.document.STYLE_INDETERMINATE else None @tracking.setter def tracking(self, value: float): - if self._initialized: - self._label.set_style("kerning", value) - else: - raise RuntimeError("Text must be initialized") + self.label.set_style("kerning", value) def em_to_px(self, em: float) -> float: """Convert from an em value to a pixel amount. 1em is defined as ``font_size`` pt. """ - if not self._initialized: - return (em * self.font_size) * (4 / 3) - else: - raise RuntimeError("Text must be initialized") + return (em * self.font_size) * (4 / 3) def px_to_em(self, px: float) -> float: """Convert from a pixel amount to a value in ems. 1em is defined as ``font_size`` pt. """ - if not self._initialized: - return px / (4 / 3) / self.font_size - else: - raise RuntimeError("Text must be initialized") + return px / (4 / 3) / self.font_size def create_text_sprite( text: str, From d2ae9569f4c1b88d000ac05295f5150609423a7f Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 18:10:06 +0200 Subject: [PATCH 07/13] Fix label property errors --- arcade/text.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index 9f77f39217..2089b54383 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -295,6 +295,14 @@ def __init__( except Exception: self._initialized = False + @property + def label(self) -> pyglet.text.Label: + """ + The underlying pyglet.Label instance. + """ + self._init_deferred() + return self._label + def initialize(self) -> None: """ Manually initialize the Text if it was lazy loaded. @@ -309,8 +317,10 @@ def _init_deferred(self): Deferred initialization when lazy loaded """ + arcade.get_window() + self._arguments[7] = _attempt_font_name_resolution(self._arguments[7]) - self.label = pyglet.text.Label( + self._label = pyglet.text.Label( text=self._arguments[0], x=self._arguments[1], y=self._arguments[2], @@ -333,16 +343,6 @@ def _init_deferred(self): self._initialized = True - @property - def label(self) -> pyglet.text.Label | None: - """ - The underlying pyglet.Label instance. - """ - if self._initialized: - return self._label - else: - raise RuntimeError("Text has not been initialized.") - def __enter__(self): """ Update multiple attributes of this text, @@ -646,6 +646,7 @@ def draw(self) -> None: instance. For information on how to do this, see :ref:`sprite_move_scrolling`. """ + self._init_deferred() _draw_pyglet_label(self.label) def draw_debug( From 47de6710d18a14f217c93f18a5615aa6e9d083bf Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 18:21:23 +0200 Subject: [PATCH 08/13] Fix docstring and formatting --- arcade/text.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index 2089b54383..4cc137ba3b 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -156,7 +156,6 @@ def _draw_pyglet_label(label: pyglet.text.Label) -> None: assert isinstance(label, pyglet.text.Label) label.draw() - class Text: """ An object-oriented way to draw text to the screen. @@ -194,7 +193,7 @@ class Text: The text instances an also be modified while in the batch such as changing the text value, position, or color. - The constructor _arguments work identically to those of + The constructor arguments work identically to those of :py:func:`~arcade.draw_text`. See its documentation for in-depth explanation for how to use each of them. For example code, see :ref:`drawing_text_objects`. @@ -223,7 +222,7 @@ class Text: group: The specific group in a a batch to add the text to (for batch rendering text) - All constructor _arguments other than ``text`` have a corresponding + All constructor arguments other than ``text`` have a corresponding property. To access the current text, use the ``value`` property instead. From c24ec87408bf95b576c2606f2b8ad89130ec51d3 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 18:26:59 +0200 Subject: [PATCH 09/13] Fix formatting --- arcade/text.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index 4cc137ba3b..d0f32f4d3b 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -156,6 +156,7 @@ def _draw_pyglet_label(label: pyglet.text.Label) -> None: assert isinstance(label, pyglet.text.Label) label.draw() + class Text: """ An object-oriented way to draw text to the screen. @@ -276,8 +277,25 @@ def __init__( **kwargs, ): self._initialized = False - self._arguments = [text, x, y, color, font_size, width, align, font_name, bold, - italic, anchor_x, anchor_y, multiline, rotation, batch, group, z] + self._arguments = [ + text, + x, + y, + color, + font_size, + width, + align, + font_name, + bold, + italic, + anchor_x, + anchor_y, + multiline, + rotation, + batch, + group, + z, + ] self._kwargs = kwargs if align not in ("left", "center", "right"): @@ -732,6 +750,7 @@ def px_to_em(self, px: float) -> float: """ return px / (4 / 3) / self.font_size + def create_text_sprite( text: str, color: RGBOrA255 = arcade.color.WHITE, From 943d87368d63190d983d0914ceb07c5f7263e07a Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 18:36:53 +0200 Subject: [PATCH 10/13] Only initialize label if its not already --- arcade/text.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index d0f32f4d3b..f6960d25aa 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -317,7 +317,8 @@ def label(self) -> pyglet.text.Label: """ The underlying pyglet.Label instance. """ - self._init_deferred() + if not self._initialized: + self._init_deferred() return self._label def initialize(self) -> None: From 50cf86fc999b875a99bdcc7eba50b1ee9ec8e8b1 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 18:49:42 +0200 Subject: [PATCH 11/13] Use a dict for both args and kwargs --- arcade/text.py | 63 ++++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/arcade/text.py b/arcade/text.py index f6960d25aa..08c4be9860 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -277,26 +277,26 @@ def __init__( **kwargs, ): self._initialized = False - self._arguments = [ - text, - x, - y, - color, - font_size, - width, - align, - font_name, - bold, - italic, - anchor_x, - anchor_y, - multiline, - rotation, - batch, - group, - z, - ] - self._kwargs = kwargs + self._arguments = dict( + text=text, + x=x, + y=y, + color=Color.from_iterable(color), + font_size=font_size, + width=width, + align=align, + font_name=font_name, + weight=pyglet.text.Weight.BOLD if bold else pyglet.text.Weight.NORMAL, + italic=italic, + anchor_x=anchor_x, + anchor_y=anchor_y, + multiline=multiline, + rotation=rotation, + batch=batch, + group=group, + z=z, + **kwargs, + ) if align not in ("left", "center", "right"): raise ValueError("The 'align' parameter must be equal to 'left', 'right', or 'center'.") @@ -337,27 +337,8 @@ def _init_deferred(self): arcade.get_window() - self._arguments[7] = _attempt_font_name_resolution(self._arguments[7]) - self._label = pyglet.text.Label( - text=self._arguments[0], - x=self._arguments[1], - y=self._arguments[2], - color=Color.from_iterable(self._arguments[3]), - font_size=self._arguments[4], - width=self._arguments[5], - align=self._arguments[6], - font_name=self._arguments[7], - weight=pyglet.text.Weight.BOLD if self._arguments[8] else pyglet.text.Weight.NORMAL, - italic=self._arguments[9], - anchor_x=self._arguments[10], - anchor_y=self._arguments[11], - multiline=self._arguments[12], - rotation=self._arguments[13], - batch=self._arguments[14], - group=self._arguments[15], - z=self._arguments[16], - **self._kwargs, - ) + self._arguments["font_name"] = _attempt_font_name_resolution(self._arguments["font_name"]) + self._label = pyglet.text.Label(**self._arguments) self._initialized = True From e48a3cb28ee901df6fc2c3fe07ffaa43ae058556 Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 19:08:41 +0200 Subject: [PATCH 12/13] Fix font_name typing issue --- arcade/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index 08c4be9860..96dfa60c8f 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -337,7 +337,7 @@ def _init_deferred(self): arcade.get_window() - self._arguments["font_name"] = _attempt_font_name_resolution(self._arguments["font_name"]) + self._arguments["font_name"] = _attempt_font_name_resolution(self._arguments["font_name"]) # type: ignore self._label = pyglet.text.Label(**self._arguments) self._initialized = True From dd480ec712c86632e6cfce8b05b55636dd61093f Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Thu, 3 Apr 2025 19:11:21 +0200 Subject: [PATCH 13/13] fix pyglet.text.Label typing issue --- arcade/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arcade/text.py b/arcade/text.py index 96dfa60c8f..0a521f83f6 100644 --- a/arcade/text.py +++ b/arcade/text.py @@ -338,7 +338,7 @@ def _init_deferred(self): arcade.get_window() self._arguments["font_name"] = _attempt_font_name_resolution(self._arguments["font_name"]) # type: ignore - self._label = pyglet.text.Label(**self._arguments) + self._label = pyglet.text.Label(**self._arguments) # type: ignore self._initialized = True